Commit be047ff7 authored by Marcel Stör's avatar Marcel Stör
Browse files

Merge branch 'dev', 1.5.4.1 master drop

parents b580bfe7 a8d984ae
/*
* params_test.h
*
* Created on: May 26, 2013
* Author: petera
*/
#ifndef PARAMS_TEST_H_
#define PARAMS_TEST_H_
// total emulated spi flash size
#define PHYS_FLASH_SIZE (16*1024*1024)
// spiffs file system size
#define SPIFFS_FLASH_SIZE (2*1024*1024)
// spiffs file system offset in emulated spi flash
#define SPIFFS_PHYS_ADDR (4*1024*1024)
#define SECTOR_SIZE 65536
#define LOG_BLOCK (SECTOR_SIZE*2)
#define LOG_PAGE (SECTOR_SIZE/256)
#define FD_BUF_SIZE 64*6
#define CACHE_BUF_SIZE (LOG_PAGE + 32)*8
#define ASSERT(c, m) real_assert((c),(m), __FILE__, __LINE__);
typedef signed int s32_t;
typedef unsigned int u32_t;
typedef signed short s16_t;
typedef unsigned short u16_t;
typedef signed char s8_t;
typedef unsigned char u8_t;
void real_assert(int c, const char *n, const char *file, int l);
#endif /* PARAMS_TEST_H_ */
/*
* test_bugreports.c
*
* Created on: Mar 8, 2015
* Author: petera
*/
#include "testrunner.h"
#include "test_spiffs.h"
#include "spiffs_nucleus.h"
#include "spiffs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
SUITE(bug_tests)
void setup() {
_setup_test_only();
}
void teardown() {
_teardown();
}
TEST(nodemcu_full_fs_1) {
fs_reset_specific(0, 4096*20, 4096, 4096, 256);
int res;
spiffs_file fd;
printf(" fill up system by writing one byte a lot\n");
fd = SPIFFS_open(FS, "test1.txt", SPIFFS_RDWR | SPIFFS_CREAT | SPIFFS_TRUNC, 0);
TEST_CHECK(fd > 0);
int i;
spiffs_stat s;
res = SPIFFS_OK;
for (i = 0; i < 100*1000; i++) {
u8_t buf = 'x';
res = SPIFFS_write(FS, fd, &buf, 1);
}
int errno = SPIFFS_errno(FS);
int res2 = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res2 == SPIFFS_OK);
printf(" >>> file %s size: %i\n", s.name, s.size);
TEST_CHECK(errno == SPIFFS_ERR_FULL);
SPIFFS_close(FS, fd);
printf(" remove big file\n");
res = SPIFFS_remove(FS, "test1.txt");
printf("res:%i errno:%i\n",res, SPIFFS_errno(FS));
TEST_CHECK(res == SPIFFS_OK);
res2 = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res2 == -1);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res2 = SPIFFS_stat(FS, "test1.txt", &s);
TEST_CHECK(res2 == -1);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_NOT_FOUND);
printf(" create small file\n");
fd = SPIFFS_open(FS, "test2.txt", SPIFFS_RDWR | SPIFFS_CREAT | SPIFFS_TRUNC, 0);
TEST_CHECK(fd > 0);
res = SPIFFS_OK;
for (i = 0; res >= 0 && i < 1000; i++) {
u8_t buf = 'x';
res = SPIFFS_write(FS, fd, &buf, 1);
}
TEST_CHECK(res >= SPIFFS_OK);
res2 = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res2 == SPIFFS_OK);
printf(" >>> file %s size: %i\n", s.name, s.size);
TEST_CHECK(s.size == 1000);
SPIFFS_close(FS, fd);
return TEST_RES_OK;
} TEST_END(nodemcu_full_fs_1)
TEST(nodemcu_full_fs_2) {
fs_reset_specific(0, 4096*22, 4096, 4096, 256);
int res;
spiffs_file fd;
printf(" fill up system by writing one byte a lot\n");
fd = SPIFFS_open(FS, "test1.txt", SPIFFS_RDWR | SPIFFS_CREAT | SPIFFS_TRUNC, 0);
TEST_CHECK(fd > 0);
int i;
spiffs_stat s;
res = SPIFFS_OK;
for (i = 0; i < 100*1000; i++) {
u8_t buf = 'x';
res = SPIFFS_write(FS, fd, &buf, 1);
}
int errno = SPIFFS_errno(FS);
int res2 = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res2 == SPIFFS_OK);
printf(" >>> file %s size: %i\n", s.name, s.size);
TEST_CHECK(errno == SPIFFS_ERR_FULL);
SPIFFS_close(FS, fd);
res2 = SPIFFS_stat(FS, "test1.txt", &s);
TEST_CHECK(res2 == SPIFFS_OK);
SPIFFS_clearerr(FS);
printf(" create small file\n");
fd = SPIFFS_open(FS, "test2.txt", SPIFFS_RDWR | SPIFFS_CREAT | SPIFFS_TRUNC, 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_OK);
TEST_CHECK(fd > 0);
for (i = 0; i < 1000; i++) {
u8_t buf = 'x';
res = SPIFFS_write(FS, fd, &buf, 1);
}
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FULL);
res2 = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res2 == SPIFFS_OK);
printf(" >>> file %s size: %i\n", s.name, s.size);
TEST_CHECK(s.size == 0);
SPIFFS_clearerr(FS);
printf(" remove files\n");
res = SPIFFS_remove(FS, "test1.txt");
TEST_CHECK(res == SPIFFS_OK);
res = SPIFFS_remove(FS, "test2.txt");
TEST_CHECK(res == SPIFFS_OK);
printf(" create medium file\n");
fd = SPIFFS_open(FS, "test3.txt", SPIFFS_RDWR | SPIFFS_CREAT | SPIFFS_TRUNC, 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_OK);
TEST_CHECK(fd > 0);
for (i = 0; i < 20*1000; i++) {
u8_t buf = 'x';
res = SPIFFS_write(FS, fd, &buf, 1);
}
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_OK);
res2 = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res2 == SPIFFS_OK);
printf(" >>> file %s size: %i\n", s.name, s.size);
TEST_CHECK(s.size == 20*1000);
return TEST_RES_OK;
} TEST_END(nodemcu_full_fs_2)
SUITE_END(bug_tests)
/*
* test_dev.c
*
* Created on: Jul 14, 2013
* Author: petera
*/
#include "testrunner.h"
#include "test_spiffs.h"
#include "spiffs_nucleus.h"
#include "spiffs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
SUITE(check_tests)
void setup() {
_setup();
}
void teardown() {
_teardown();
}
TEST(evil_write) {
fs_set_validate_flashing(0);
printf("writing corruption to block 1 data range (leaving lu intact)\n");
u32_t data_range = SPIFFS_CFG_LOG_BLOCK_SZ(FS) -
SPIFFS_CFG_LOG_PAGE_SZ(FS) * (SPIFFS_OBJ_LOOKUP_PAGES(FS));
u8_t *corruption = malloc(data_range);
memrand(corruption, data_range);
u32_t addr = 0 * SPIFFS_CFG_LOG_PAGE_SZ(FS) * SPIFFS_OBJ_LOOKUP_PAGES(FS);
area_write(addr, corruption, data_range);
free(corruption);
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
printf("CHECK1-----------------\n");
SPIFFS_check(FS);
printf("CHECK2-----------------\n");
SPIFFS_check(FS);
printf("CHECK3-----------------\n");
SPIFFS_check(FS);
res = test_create_and_write_file("file2", size, size);
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END(evil_write)
TEST(lu_check1) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify lu entry data page index 1
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 1, 0, &pix);
TEST_CHECK(res >= 0);
// reset lu entry to being erased, but keep page data
spiffs_obj_id obj_id = SPIFFS_OBJ_ID_DELETED;
spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(FS, pix);
int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(FS, pix);
u32_t addr = SPIFFS_BLOCK_TO_PADDR(FS, bix) + entry*sizeof(spiffs_obj_id);
area_write(addr, (u8_t*)&obj_id, sizeof(spiffs_obj_id));
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
return TEST_RES_OK;
} TEST_END(lu_check1)
TEST(page_cons1) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify object index, find object index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
// set object index entry 2 to a bad page
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + sizeof(spiffs_page_object_ix_header) + 0 * sizeof(spiffs_page_ix);
spiffs_page_ix bad_pix_ref = 0x55;
area_write(addr, (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
area_write(addr+2, (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
// delete all cache
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END(page_cons1)
TEST(page_cons2) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify object index, find object index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
// find data page span index 0
spiffs_page_ix dpix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &dpix);
TEST_CHECK(res >= 0);
// set object index entry 1+2 to a data page 0
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + sizeof(spiffs_page_object_ix_header) + 1 * sizeof(spiffs_page_ix);
spiffs_page_ix bad_pix_ref = dpix;
area_write(addr, (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
area_write(addr+sizeof(spiffs_page_ix), (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
// delete all cache
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END(page_cons2)
TEST(page_cons3) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify object index, find object index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
// set object index entry 1+2 lookup page
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + sizeof(spiffs_page_object_ix_header) + 1 * sizeof(spiffs_page_ix);
spiffs_page_ix bad_pix_ref = SPIFFS_PAGES_PER_BLOCK(FS) * (*FS.block_count - 2);
area_write(addr, (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
area_write(addr+sizeof(spiffs_page_ix), (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
// delete all cache
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END(page_cons3)
TEST(page_cons_final) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify page header, make unfinalized
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id & ~SPIFFS_OBJ_ID_IX_FLAG, 1, 0, &pix);
TEST_CHECK(res >= 0);
// set page span ix 1 as unfinalized
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + offsetof(spiffs_page_header, flags);
u8_t flags;
area_read(addr, (u8_t*)&flags, 1);
flags |= SPIFFS_PH_FLAG_FINAL;
area_write(addr, (u8_t*)&flags, 1);
// delete all cache
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END(page_cons_final)
TEST(index_cons1) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*SPIFFS_PAGES_PER_BLOCK(FS);
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify lu entry data page index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
printf(" deleting lu entry pix %04x\n", pix);
// reset lu entry to being erased, but keep page data
spiffs_obj_id obj_id = SPIFFS_OBJ_ID_DELETED;
spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(FS, pix);
int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(FS, pix);
u32_t addr = SPIFFS_BLOCK_TO_PADDR(FS, bix) + entry * sizeof(spiffs_obj_id);
area_write(addr, (u8_t*)&obj_id, sizeof(spiffs_obj_id));
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END(index_cons1)
TEST(index_cons2) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*SPIFFS_PAGES_PER_BLOCK(FS);
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify lu entry data page index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
printf(" writing lu entry for index page, ix %04x, as data page\n", pix);
spiffs_obj_id obj_id = 0x1234;
spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(FS, pix);
int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(FS, pix);
u32_t addr = SPIFFS_BLOCK_TO_PADDR(FS, bix) + entry * sizeof(spiffs_obj_id);
area_write(addr, (u8_t*)&obj_id, sizeof(spiffs_obj_id));
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END(index_cons2)
TEST(index_cons3) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*SPIFFS_PAGES_PER_BLOCK(FS);
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify lu entry data page index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
printf(" setting lu entry pix %04x to another index page\n", pix);
// reset lu entry to being erased, but keep page data
spiffs_obj_id obj_id = 1234 | SPIFFS_OBJ_ID_IX_FLAG;
spiffs_block_ix bix = SPIFFS_BLOCK_FOR_PAGE(FS, pix);
int entry = SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(FS, pix);
u32_t addr = SPIFFS_BLOCK_TO_PADDR(FS, bix) + entry * sizeof(spiffs_obj_id);
area_write(addr, (u8_t*)&obj_id, sizeof(spiffs_obj_id));
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
} TEST_END(index_cons3)
TEST(index_cons4) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*SPIFFS_PAGES_PER_BLOCK(FS);
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify lu entry data page index header, flags
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
printf(" cue objix hdr deletion in page %04x\n", pix);
// set flags as deleting ix header
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + offsetof(spiffs_page_header, flags);
u8_t flags = 0xff & ~(SPIFFS_PH_FLAG_FINAL | SPIFFS_PH_FLAG_USED | SPIFFS_PH_FLAG_INDEX | SPIFFS_PH_FLAG_IXDELE);
area_write(addr, (u8_t*)&flags, 1);
#if SPIFFS_CACHE
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
SPIFFS_check(FS);
return TEST_RES_OK;
} TEST_END(index_cons4)
SUITE_END(check_tests)
/*
* test_dev.c
*
* Created on: Jul 14, 2013
* Author: petera
*/
#include "testrunner.h"
#include "test_spiffs.h"
#include "spiffs_nucleus.h"
#include "spiffs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
SUITE(dev_tests)
void setup() {
_setup();
}
void teardown() {
_teardown();
}
TEST(interrupted_write) {
char *name = "interrupt";
char *name2 = "interrupt2";
int res;
spiffs_file fd;
const u32_t sz = SPIFFS_CFG_LOG_PAGE_SZ(FS)*8;
u8_t *buf = malloc(sz);
memrand(buf, sz);
printf(" create reference file\n");
fd = SPIFFS_open(FS, name, SPIFFS_RDWR | SPIFFS_CREAT | SPIFFS_TRUNC, 0);
TEST_CHECK(fd > 0);
clear_flash_ops_log();
res = SPIFFS_write(FS, fd, buf, sz);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
u32_t written = get_flash_ops_log_write_bytes();
printf(" written bytes: %i\n", written);
printf(" create error file\n");
fd = SPIFFS_open(FS, name2, SPIFFS_RDWR | SPIFFS_CREAT | SPIFFS_TRUNC, 0);
TEST_CHECK(fd > 0);
clear_flash_ops_log();
invoke_error_after_write_bytes(written/2, 0);
res = SPIFFS_write(FS, fd, buf, sz);
SPIFFS_close(FS, fd);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_TEST);
clear_flash_ops_log();
#if SPIFFS_CACHE
// delete all cache
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
printf(" read error file\n");
fd = SPIFFS_open(FS, name2, SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
printf(" file size: %i\n", s.size);
if (s.size > 0) {
u8_t *buf2 = malloc(s.size);
res = SPIFFS_read(FS, fd, buf2, s.size);
TEST_CHECK(res >= 0);
u32_t ix = 0;
for (ix = 0; ix < s.size; ix += 16) {
int i;
printf(" ");
for (i = 0; i < 16; i++) {
printf("%02x", buf[ix+i]);
}
printf(" ");
for (i = 0; i < 16; i++) {
printf("%02x", buf2[ix+i]);
}
printf("\n");
}
free(buf2);
}
SPIFFS_close(FS, fd);
printf(" FS check\n");
SPIFFS_check(FS);
printf(" read error file again\n");
fd = SPIFFS_open(FS, name2, SPIFFS_APPEND | SPIFFS_RDWR, 0);
TEST_CHECK(fd > 0);
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
printf(" file size: %i\n", s.size);
printf(" write file\n");
res = SPIFFS_write(FS, fd, buf, sz);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
free(buf);
return TEST_RES_OK;
} TEST_END(interrupted_write)
SUITE_END(dev_tests)
/*
* test_suites.c
*
* Created on: Jun 19, 2013
* Author: petera
*/
#include "testrunner.h"
#include "test_spiffs.h"
#include "spiffs_nucleus.h"
#include "spiffs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
SUITE(hydrogen_tests)
void setup() {
_setup();
}
void teardown() {
_teardown();
}
TEST(info)
{
u32_t used, total;
int res = SPIFFS_info(FS, &total, &used);
TEST_CHECK(res == SPIFFS_OK);
TEST_CHECK(used == 0);
TEST_CHECK(total < __fs.cfg.phys_size);
return TEST_RES_OK;
}
TEST_END(info)
TEST(missing_file)
{
spiffs_file fd = SPIFFS_open(FS, "this_wont_exist", SPIFFS_RDONLY, 0);
TEST_CHECK(fd < 0);
return TEST_RES_OK;
}
TEST_END(missing_file)
TEST(bad_fd)
{
int res;
spiffs_stat s;
spiffs_file fd = SPIFFS_open(FS, "this_wont_exist", SPIFFS_RDONLY, 0);
TEST_CHECK(fd < 0);
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_BAD_DESCRIPTOR);
res = SPIFFS_fremove(FS, fd);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_BAD_DESCRIPTOR);
res = SPIFFS_lseek(FS, fd, 0, SPIFFS_SEEK_CUR);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_BAD_DESCRIPTOR);
res = SPIFFS_read(FS, fd, 0, 0);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_BAD_DESCRIPTOR);
res = SPIFFS_write(FS, fd, 0, 0);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_BAD_DESCRIPTOR);
return TEST_RES_OK;
}
TEST_END(bad_fd)
TEST(closed_fd)
{
int res;
spiffs_stat s;
res = test_create_file("file");
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd >= 0);
SPIFFS_close(FS, fd);
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_fremove(FS, fd);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_lseek(FS, fd, 0, SPIFFS_SEEK_CUR);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_read(FS, fd, 0, 0);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_write(FS, fd, 0, 0);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
return TEST_RES_OK;
}
TEST_END(closed_fd)
TEST(deleted_same_fd)
{
int res;
spiffs_stat s;
spiffs_file fd;
res = test_create_file("remove");
fd = SPIFFS_open(FS, "remove", SPIFFS_RDWR, 0);
TEST_CHECK(fd >= 0);
fd = SPIFFS_open(FS, "remove", SPIFFS_RDWR, 0);
TEST_CHECK(fd >= 0);
res = SPIFFS_fremove(FS, fd);
TEST_CHECK(res >= 0);
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_fremove(FS, fd);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_lseek(FS, fd, 0, SPIFFS_SEEK_CUR);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_read(FS, fd, 0, 0);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_write(FS, fd, 0, 0);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
return TEST_RES_OK;
}
TEST_END(deleted_same_fd)
TEST(deleted_other_fd)
{
int res;
spiffs_stat s;
spiffs_file fd, fd_orig;
res = test_create_file("remove");
fd_orig = SPIFFS_open(FS, "remove", SPIFFS_RDWR, 0);
TEST_CHECK(fd_orig >= 0);
fd = SPIFFS_open(FS, "remove", SPIFFS_RDWR, 0);
TEST_CHECK(fd >= 0);
res = SPIFFS_fremove(FS, fd_orig);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd_orig);
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_fremove(FS, fd);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_lseek(FS, fd, 0, SPIFFS_SEEK_CUR);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_read(FS, fd, 0, 0);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
res = SPIFFS_write(FS, fd, 0, 0);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_FILE_CLOSED);
return TEST_RES_OK;
}
TEST_END(deleted_other_fd)
TEST(file_by_open)
{
int res;
spiffs_stat s;
spiffs_file fd = SPIFFS_open(FS, "filebopen", SPIFFS_CREAT, 0);
TEST_CHECK(fd >= 0);
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
TEST_CHECK(strcmp((char*)s.name, "filebopen") == 0);
TEST_CHECK(s.size == 0);
SPIFFS_close(FS, fd);
fd = SPIFFS_open(FS, "filebopen", SPIFFS_RDONLY, 0);
TEST_CHECK(fd >= 0);
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
TEST_CHECK(strcmp((char*)s.name, "filebopen") == 0);
TEST_CHECK(s.size == 0);
SPIFFS_close(FS, fd);
return TEST_RES_OK;
}
TEST_END(file_by_open)
TEST(file_by_creat)
{
int res;
res = test_create_file("filebcreat");
TEST_CHECK(res >= 0);
res = SPIFFS_creat(FS, "filebcreat", 0);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS)==SPIFFS_ERR_CONFLICTING_NAME);
return TEST_RES_OK;
}
TEST_END(file_by_creat)
TEST(list_dir)
{
int res;
char *files[4] = {
"file1",
"file2",
"file3",
"file4"
};
int file_cnt = sizeof(files)/sizeof(char *);
int i;
for (i = 0; i < file_cnt; i++) {
res = test_create_file(files[i]);
TEST_CHECK(res >= 0);
}
spiffs_DIR d;
struct spiffs_dirent e;
struct spiffs_dirent *pe = &e;
SPIFFS_opendir(FS, "/", &d);
int found = 0;
while ((pe = SPIFFS_readdir(&d, pe))) {
printf(" %s [%04x] size:%i\n", pe->name, pe->obj_id, pe->size);
for (i = 0; i < file_cnt; i++) {
if (strcmp(files[i], pe->name) == 0) {
found++;
break;
}
}
}
SPIFFS_closedir(&d);
TEST_CHECK(found == file_cnt);
return TEST_RES_OK;
}
TEST_END(list_dir)
TEST(open_by_dirent) {
int res;
char *files[4] = {
"file1",
"file2",
"file3",
"file4"
};
int file_cnt = sizeof(files)/sizeof(char *);
int i;
int size = SPIFFS_DATA_PAGE_SIZE(FS);
for (i = 0; i < file_cnt; i++) {
res = test_create_and_write_file(files[i], size, size);
TEST_CHECK(res >= 0);
}
spiffs_DIR d;
struct spiffs_dirent e;
struct spiffs_dirent *pe = &e;
int found = 0;
SPIFFS_opendir(FS, "/", &d);
while ((pe = SPIFFS_readdir(&d, pe))) {
spiffs_file fd = SPIFFS_open_by_dirent(FS, pe, SPIFFS_RDWR, 0);
TEST_CHECK(fd >= 0);
res = read_and_verify_fd(fd, pe->name);
TEST_CHECK(res == SPIFFS_OK);
fd = SPIFFS_open_by_dirent(FS, pe, SPIFFS_RDWR, 0);
TEST_CHECK(fd >= 0);
res = SPIFFS_fremove(FS, fd);
TEST_CHECK(res == SPIFFS_OK);
SPIFFS_close(FS, fd);
found++;
}
SPIFFS_closedir(&d);
TEST_CHECK(found == file_cnt);
found = 0;
SPIFFS_opendir(FS, "/", &d);
while ((pe = SPIFFS_readdir(&d, pe))) {
found++;
}
SPIFFS_closedir(&d);
TEST_CHECK(found == 0);
return TEST_RES_OK;
} TEST_END(open_by_dirent)
TEST(rename) {
int res;
char *src_name = "baah";
char *dst_name = "booh";
char *dst_name2 = "beeh";
int size = SPIFFS_DATA_PAGE_SIZE(FS);
res = test_create_and_write_file(src_name, size, size);
TEST_CHECK(res >= 0);
res = SPIFFS_rename(FS, src_name, dst_name);
TEST_CHECK(res >= 0);
res = SPIFFS_rename(FS, dst_name, dst_name);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_CONFLICTING_NAME);
res = SPIFFS_rename(FS, src_name, dst_name2);
TEST_CHECK(res < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_NOT_FOUND);
return TEST_RES_OK;
} TEST_END(rename)
TEST(remove_single_by_path)
{
int res;
spiffs_file fd;
res = test_create_file("remove");
TEST_CHECK(res >= 0);
res = SPIFFS_remove(FS, "remove");
TEST_CHECK(res >= 0);
fd = SPIFFS_open(FS, "remove", SPIFFS_RDONLY, 0);
TEST_CHECK(fd < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_NOT_FOUND);
return TEST_RES_OK;
}
TEST_END(remove_single_by_path)
TEST(remove_single_by_fd)
{
int res;
spiffs_file fd;
res = test_create_file("remove");
TEST_CHECK(res >= 0);
fd = SPIFFS_open(FS, "remove", SPIFFS_RDWR, 0);
TEST_CHECK(fd >= 0);
res = SPIFFS_fremove(FS, fd);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
fd = SPIFFS_open(FS, "remove", SPIFFS_RDONLY, 0);
TEST_CHECK(fd < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_NOT_FOUND);
return TEST_RES_OK;
}
TEST_END(remove_single_by_fd)
TEST(write_big_file_chunks_page)
{
int size = ((50*(FS)->cfg.phys_size)/100);
printf(" filesize %i\n", size);
int res = test_create_and_write_file("bigfile", size, SPIFFS_DATA_PAGE_SIZE(FS));
TEST_CHECK(res >= 0);
res = read_and_verify("bigfile");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
}
TEST_END(write_big_file_chunks_page)
TEST(write_big_files_chunks_page)
{
char name[32];
int f;
int files = 10;
int res;
int size = ((50*(FS)->cfg.phys_size)/100)/files;
printf(" filesize %i\n", size);
for (f = 0; f < files; f++) {
sprintf(name, "bigfile%i", f);
res = test_create_and_write_file(name, size, SPIFFS_DATA_PAGE_SIZE(FS));
TEST_CHECK(res >= 0);
}
for (f = 0; f < files; f++) {
sprintf(name, "bigfile%i", f);
res = read_and_verify(name);
TEST_CHECK(res >= 0);
}
return TEST_RES_OK;
}
TEST_END(write_big_files_chunks_page)
TEST(write_big_file_chunks_index)
{
int size = ((50*(FS)->cfg.phys_size)/100);
printf(" filesize %i\n", size);
int res = test_create_and_write_file("bigfile", size, SPIFFS_DATA_PAGE_SIZE(FS) * SPIFFS_OBJ_HDR_IX_LEN(FS));
TEST_CHECK(res >= 0);
res = read_and_verify("bigfile");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
}
TEST_END(write_big_file_chunks_index)
TEST(write_big_files_chunks_index)
{
char name[32];
int f;
int files = 10;
int res;
int size = ((50*(FS)->cfg.phys_size)/100)/files;
printf(" filesize %i\n", size);
for (f = 0; f < files; f++) {
sprintf(name, "bigfile%i", f);
res = test_create_and_write_file(name, size, SPIFFS_DATA_PAGE_SIZE(FS) * SPIFFS_OBJ_HDR_IX_LEN(FS));
TEST_CHECK(res >= 0);
}
for (f = 0; f < files; f++) {
sprintf(name, "bigfile%i", f);
res = read_and_verify(name);
TEST_CHECK(res >= 0);
}
return TEST_RES_OK;
}
TEST_END(write_big_files_chunks_index)
TEST(write_big_file_chunks_huge)
{
int size = (FS_PURE_DATA_PAGES(FS) / 2) * SPIFFS_DATA_PAGE_SIZE(FS);
printf(" filesize %i\n", size);
int res = test_create_and_write_file("bigfile", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("bigfile");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
}
TEST_END(write_big_file_chunks_huge)
TEST(write_big_files_chunks_huge)
{
char name[32];
int f;
int files = 10;
int res;
int size = ((50*(FS)->cfg.phys_size)/100)/files;
printf(" filesize %i\n", size);
for (f = 0; f < files; f++) {
sprintf(name, "bigfile%i", f);
res = test_create_and_write_file(name, size, size);
TEST_CHECK(res >= 0);
}
for (f = 0; f < files; f++) {
sprintf(name, "bigfile%i", f);
res = read_and_verify(name);
TEST_CHECK(res >= 0);
}
return TEST_RES_OK;
}
TEST_END(write_big_files_chunks_huge)
TEST(truncate_big_file)
{
int size = (FS_PURE_DATA_PAGES(FS) / 2) * SPIFFS_DATA_PAGE_SIZE(FS);
printf(" filesize %i\n", size);
int res = test_create_and_write_file("bigfile", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("bigfile");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "bigfile", SPIFFS_RDWR, 0);
TEST_CHECK(fd > 0);
res = SPIFFS_fremove(FS, fd);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
fd = SPIFFS_open(FS, "bigfile", SPIFFS_RDWR, 0);
TEST_CHECK(fd < 0);
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_NOT_FOUND);
return TEST_RES_OK;
}
TEST_END(truncate_big_file)
TEST(simultaneous_write) {
int res = SPIFFS_creat(FS, "simul1", 0);
TEST_CHECK(res >= 0);
spiffs_file fd1 = SPIFFS_open(FS, "simul1", SPIFFS_RDWR, 0);
TEST_CHECK(fd1 > 0);
spiffs_file fd2 = SPIFFS_open(FS, "simul1", SPIFFS_RDWR, 0);
TEST_CHECK(fd2 > 0);
spiffs_file fd3 = SPIFFS_open(FS, "simul1", SPIFFS_RDWR, 0);
TEST_CHECK(fd3 > 0);
u8_t data1 = 1;
u8_t data2 = 2;
u8_t data3 = 3;
res = SPIFFS_write(FS, fd1, &data1, 1);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd1);
res = SPIFFS_write(FS, fd2, &data2, 1);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd2);
res = SPIFFS_write(FS, fd3, &data3, 1);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd3);
spiffs_stat s;
res = SPIFFS_stat(FS, "simul1", &s);
TEST_CHECK(res >= 0);
TEST_CHECK(s.size == 1);
u8_t rdata;
spiffs_file fd = SPIFFS_open(FS, "simul1", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
res = SPIFFS_read(FS, fd, &rdata, 1);
TEST_CHECK(res >= 0);
TEST_CHECK(rdata == data3);
return TEST_RES_OK;
}
TEST_END(simultaneous_write)
TEST(simultaneous_write_append) {
int res = SPIFFS_creat(FS, "simul2", 0);
TEST_CHECK(res >= 0);
spiffs_file fd1 = SPIFFS_open(FS, "simul2", SPIFFS_RDWR | SPIFFS_APPEND, 0);
TEST_CHECK(fd1 > 0);
spiffs_file fd2 = SPIFFS_open(FS, "simul2", SPIFFS_RDWR | SPIFFS_APPEND, 0);
TEST_CHECK(fd2 > 0);
spiffs_file fd3 = SPIFFS_open(FS, "simul2", SPIFFS_RDWR | SPIFFS_APPEND, 0);
TEST_CHECK(fd3 > 0);
u8_t data1 = 1;
u8_t data2 = 2;
u8_t data3 = 3;
res = SPIFFS_write(FS, fd1, &data1, 1);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd1);
res = SPIFFS_write(FS, fd2, &data2, 1);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd2);
res = SPIFFS_write(FS, fd3, &data3, 1);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd3);
spiffs_stat s;
res = SPIFFS_stat(FS, "simul2", &s);
TEST_CHECK(res >= 0);
TEST_CHECK(s.size == 3);
u8_t rdata[3];
spiffs_file fd = SPIFFS_open(FS, "simul2", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
res = SPIFFS_read(FS, fd, &rdata, 3);
TEST_CHECK(res >= 0);
TEST_CHECK(rdata[0] == data1);
TEST_CHECK(rdata[1] == data2);
TEST_CHECK(rdata[2] == data3);
return TEST_RES_OK;
}
TEST_END(simultaneous_write_append)
TEST(file_uniqueness)
{
int res;
spiffs_file fd;
char fname[32];
int files = ((SPIFFS_CFG_PHYS_SZ(FS) * 75) / 100) / 2 / SPIFFS_CFG_LOG_PAGE_SZ(FS);
//(FS_PURE_DATA_PAGES(FS) / 2) - SPIFFS_PAGES_PER_BLOCK(FS)*8;
int i;
printf(" creating %i files\n", files);
for (i = 0; i < files; i++) {
char content[20];
sprintf(fname, "file%i", i);
sprintf(content, "%i", i);
res = test_create_file(fname);
TEST_CHECK(res >= 0);
fd = SPIFFS_open(FS, fname, SPIFFS_APPEND | SPIFFS_RDWR, 0);
TEST_CHECK(fd >= 0);
res = SPIFFS_write(FS, fd, content, strlen(content)+1);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
}
printf(" checking %i files\n", files);
for (i = 0; i < files; i++) {
char content[20];
char ref_content[20];
sprintf(fname, "file%i", i);
sprintf(content, "%i", i);
fd = SPIFFS_open(FS, fname, SPIFFS_RDONLY, 0);
TEST_CHECK(fd >= 0);
res = SPIFFS_read(FS, fd, ref_content, strlen(content)+1);
TEST_CHECK(res >= 0);
TEST_CHECK(strcmp(ref_content, content) == 0);
SPIFFS_close(FS, fd);
}
printf(" removing %i files\n", files/2);
for (i = 0; i < files; i += 2) {
sprintf(fname, "file%i", i);
res = SPIFFS_remove(FS, fname);
TEST_CHECK(res >= 0);
}
printf(" creating %i files\n", files/2);
for (i = 0; i < files; i += 2) {
char content[20];
sprintf(fname, "file%i", i);
sprintf(content, "new%i", i);
res = test_create_file(fname);
TEST_CHECK(res >= 0);
fd = SPIFFS_open(FS, fname, SPIFFS_APPEND | SPIFFS_RDWR, 0);
TEST_CHECK(fd >= 0);
res = SPIFFS_write(FS, fd, content, strlen(content)+1);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
}
printf(" checking %i files\n", files);
for (i = 0; i < files; i++) {
char content[20];
char ref_content[20];
sprintf(fname, "file%i", i);
if ((i & 1) == 0) {
sprintf(content, "new%i", i);
} else {
sprintf(content, "%i", i);
}
fd = SPIFFS_open(FS, fname, SPIFFS_RDONLY, 0);
TEST_CHECK(fd >= 0);
res = SPIFFS_read(FS, fd, ref_content, strlen(content)+1);
TEST_CHECK(res >= 0);
TEST_CHECK(strcmp(ref_content, content) == 0);
SPIFFS_close(FS, fd);
}
return TEST_RES_OK;
}
TEST_END(file_uniqueness)
int create_and_read_back(int size, int chunk) {
char *name = "file";
spiffs_file fd;
s32_t res;
u8_t *buf = malloc(size);
memrand(buf, size);
res = test_create_file(name);
CHECK(res >= 0);
fd = SPIFFS_open(FS, name, SPIFFS_APPEND | SPIFFS_RDWR, 0);
CHECK(fd >= 0);
res = SPIFFS_write(FS, fd, buf, size);
CHECK(res >= 0);
spiffs_stat stat;
res = SPIFFS_fstat(FS, fd, &stat);
CHECK(res >= 0);
CHECK(stat.size == size);
SPIFFS_close(FS, fd);
fd = SPIFFS_open(FS, name, SPIFFS_RDONLY, 0);
CHECK(fd >= 0);
u8_t *rbuf = malloc(size);
int offs = 0;
while (offs < size) {
int len = MIN(size - offs, chunk);
res = SPIFFS_read(FS, fd, &rbuf[offs], len);
CHECK(res >= 0);
CHECK(memcmp(&rbuf[offs], &buf[offs], len) == 0);
offs += chunk;
}
CHECK(memcmp(&rbuf[0], &buf[0], size) == 0);
SPIFFS_close(FS, fd);
free(rbuf);
free(buf);
return 0;
}
TEST(read_chunk_1)
{
TEST_CHECK(create_and_read_back(SPIFFS_DATA_PAGE_SIZE(FS)*8, 1) == 0);
return TEST_RES_OK;
}
TEST_END(read_chunk_1)
TEST(read_chunk_page)
{
TEST_CHECK(create_and_read_back(SPIFFS_DATA_PAGE_SIZE(FS)*(SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS))*2,
SPIFFS_DATA_PAGE_SIZE(FS)) == 0);
return TEST_RES_OK;
}
TEST_END(read_chunk_page)
TEST(read_chunk_index)
{
TEST_CHECK(create_and_read_back(SPIFFS_DATA_PAGE_SIZE(FS)*(SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS))*4,
SPIFFS_DATA_PAGE_SIZE(FS)*(SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS))) == 0);
return TEST_RES_OK;
}
TEST_END(read_chunk_index)
TEST(read_chunk_huge)
{
int sz = (2*(FS)->cfg.phys_size)/3;
TEST_CHECK(create_and_read_back(sz, sz) == 0);
return TEST_RES_OK;
}
TEST_END(read_chunk_huge)
TEST(read_beyond)
{
char *name = "file";
spiffs_file fd;
s32_t res;
u32_t size = SPIFFS_DATA_PAGE_SIZE(FS)*2;
u8_t *buf = malloc(size);
memrand(buf, size);
res = test_create_file(name);
CHECK(res >= 0);
fd = SPIFFS_open(FS, name, SPIFFS_APPEND | SPIFFS_RDWR, 0);
CHECK(fd >= 0);
res = SPIFFS_write(FS, fd, buf, size);
CHECK(res >= 0);
spiffs_stat stat;
res = SPIFFS_fstat(FS, fd, &stat);
CHECK(res >= 0);
CHECK(stat.size == size);
SPIFFS_close(FS, fd);
fd = SPIFFS_open(FS, name, SPIFFS_RDONLY, 0);
CHECK(fd >= 0);
u8_t *rbuf = malloc(size+10);
res = SPIFFS_read(FS, fd, rbuf, size+10);
SPIFFS_close(FS, fd);
free(rbuf);
free(buf);
TEST_CHECK(res == size);
return TEST_RES_OK;
}
TEST_END(read_beyond)
TEST(bad_index_1) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify object index, find object index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
// set object index entry 2 to a bad page, free
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + sizeof(spiffs_page_object_ix_header) + 2 * sizeof(spiffs_page_ix);
spiffs_page_ix bad_pix_ref = (spiffs_page_ix)-1;
area_write(addr, (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
#if SPIFFS_CACHE
// delete all cache
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
res = read_and_verify("file");
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_INDEX_REF_FREE);
return TEST_RES_OK;
} TEST_END(bad_index_1)
TEST(bad_index_2) {
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
int res = test_create_and_write_file("file", size, size);
TEST_CHECK(res >= 0);
res = read_and_verify("file");
TEST_CHECK(res >= 0);
spiffs_file fd = SPIFFS_open(FS, "file", SPIFFS_RDONLY, 0);
TEST_CHECK(fd > 0);
spiffs_stat s;
res = SPIFFS_fstat(FS, fd, &s);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
// modify object index, find object index header
spiffs_page_ix pix;
res = spiffs_obj_lu_find_id_and_span(FS, s.obj_id | SPIFFS_OBJ_ID_IX_FLAG, 0, 0, &pix);
TEST_CHECK(res >= 0);
// set object index entry 2 to a bad page, lu
u32_t addr = SPIFFS_PAGE_TO_PADDR(FS, pix) + sizeof(spiffs_page_object_ix_header) + 2 * sizeof(spiffs_page_ix);
spiffs_page_ix bad_pix_ref = SPIFFS_OBJ_LOOKUP_PAGES(FS)-1;
area_write(addr, (u8_t*)&bad_pix_ref, sizeof(spiffs_page_ix));
#if SPIFFS_CACHE
// delete all cache
spiffs_cache *cache = spiffs_get_cache(FS);
cache->cpage_use_map = 0;
#endif
res = read_and_verify("file");
TEST_CHECK(SPIFFS_errno(FS) == SPIFFS_ERR_INDEX_REF_LU);
return TEST_RES_OK;
} TEST_END(bad_index_2)
TEST(lseek_simple_modification) {
int res;
spiffs_file fd;
char *fname = "seekfile";
int i;
int len = 4096;
fd = SPIFFS_open(FS, fname, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR, 0);
TEST_CHECK(fd > 0);
int pfd = open(make_test_fname(fname), O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
u8_t *buf = malloc(len);
memrand(buf, len);
res = SPIFFS_write(FS, fd, buf, len);
TEST_CHECK(res >= 0);
write(pfd, buf, len);
free(buf);
res = read_and_verify(fname);
TEST_CHECK(res >= 0);
res = SPIFFS_lseek(FS, fd, len/2, SPIFFS_SEEK_SET);
TEST_CHECK(res >= 0);
lseek(pfd, len/2, SEEK_SET);
len = len/4;
buf = malloc(len);
memrand(buf, len);
res = SPIFFS_write(FS, fd, buf, len);
TEST_CHECK(res >= 0);
write(pfd, buf, len);
free(buf);
res = read_and_verify(fname);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
close(pfd);
return TEST_RES_OK;
}
TEST_END(lseek_simple_modification)
TEST(lseek_modification_append) {
int res;
spiffs_file fd;
char *fname = "seekfile";
int i;
int len = 4096;
fd = SPIFFS_open(FS, fname, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR, 0);
TEST_CHECK(fd > 0);
int pfd = open(make_test_fname(fname), O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
u8_t *buf = malloc(len);
memrand(buf, len);
res = SPIFFS_write(FS, fd, buf, len);
TEST_CHECK(res >= 0);
write(pfd, buf, len);
free(buf);
res = read_and_verify(fname);
TEST_CHECK(res >= 0);
res = SPIFFS_lseek(FS, fd, len/2, SPIFFS_SEEK_SET);
TEST_CHECK(res >= 0);
lseek(pfd, len/2, SEEK_SET);
buf = malloc(len);
memrand(buf, len);
res = SPIFFS_write(FS, fd, buf, len);
TEST_CHECK(res >= 0);
write(pfd, buf, len);
free(buf);
res = read_and_verify(fname);
TEST_CHECK(res >= 0);
SPIFFS_close(FS, fd);
close(pfd);
return TEST_RES_OK;
}
TEST_END(lseek_modification_append)
TEST(lseek_modification_append_multi) {
int res;
spiffs_file fd;
char *fname = "seekfile";
int len = 1024;
int runs = (FS_PURE_DATA_PAGES(FS) / 2) * SPIFFS_DATA_PAGE_SIZE(FS) / (len/2);
fd = SPIFFS_open(FS, fname, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR, 0);
TEST_CHECK(fd > 0);
int pfd = open(make_test_fname(fname), O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
u8_t *buf = malloc(len);
memrand(buf, len);
res = SPIFFS_write(FS, fd, buf, len);
TEST_CHECK(res >= 0);
write(pfd, buf, len);
free(buf);
res = read_and_verify(fname);
TEST_CHECK(res >= 0);
while (runs--) {
res = SPIFFS_lseek(FS, fd, -len/2, SPIFFS_SEEK_END);
TEST_CHECK(res >= 0);
lseek(pfd, -len/2, SEEK_END);
buf = malloc(len);
memrand(buf, len);
res = SPIFFS_write(FS, fd, buf, len);
TEST_CHECK(res >= 0);
write(pfd, buf, len);
free(buf);
res = read_and_verify(fname);
TEST_CHECK(res >= 0);
}
SPIFFS_close(FS, fd);
close(pfd);
return TEST_RES_OK;
}
TEST_END(lseek_modification_append_multi)
TEST(lseek_read) {
int res;
spiffs_file fd;
char *fname = "seekfile";
int len = (FS_PURE_DATA_PAGES(FS) / 2) * SPIFFS_DATA_PAGE_SIZE(FS);
int runs = 100000;
fd = SPIFFS_open(FS, fname, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR, 0);
TEST_CHECK(fd > 0);
u8_t *refbuf = malloc(len);
memrand(refbuf, len);
res = SPIFFS_write(FS, fd, refbuf, len);
TEST_CHECK(res >= 0);
int offs = 0;
res = SPIFFS_lseek(FS, fd, 0, SPIFFS_SEEK_SET);
TEST_CHECK(res >= 0);
while (runs--) {
int i;
u8_t buf[64];
if (offs + 41 + sizeof(buf) >= len) {
offs = (offs + 41 + sizeof(buf)) % len;
res = SPIFFS_lseek(FS, fd, offs, SPIFFS_SEEK_SET);
TEST_CHECK(res >= 0);
}
res = SPIFFS_lseek(FS, fd, 41, SPIFFS_SEEK_CUR);
TEST_CHECK(res >= 0);
offs += 41;
res = SPIFFS_read(FS, fd, buf, sizeof(buf));
TEST_CHECK(res >= 0);
for (i = 0; i < sizeof(buf); i++) {
if (buf[i] != refbuf[offs+i]) {
printf(" mismatch at offs %i\n", offs);
}
TEST_CHECK(buf[i] == refbuf[offs+i]);
}
offs += sizeof(buf);
res = SPIFFS_lseek(FS, fd, -((u32_t)sizeof(buf)+11), SPIFFS_SEEK_CUR);
TEST_CHECK(res >= 0);
offs -= (sizeof(buf)+11);
res = SPIFFS_read(FS, fd, buf, sizeof(buf));
TEST_CHECK(res >= 0);
for (i = 0; i < sizeof(buf); i++) {
if (buf[i] != refbuf[offs+i]) {
printf(" mismatch at offs %i\n", offs);
}
TEST_CHECK(buf[i] == refbuf[offs+i]);
}
offs += sizeof(buf);
}
free(refbuf);
SPIFFS_close(FS, fd);
return TEST_RES_OK;
}
TEST_END(lseek_read)
TEST(write_small_file_chunks_1)
{
int res = test_create_and_write_file("smallfile", 256, 1);
TEST_CHECK(res >= 0);
res = read_and_verify("smallfile");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
}
TEST_END(write_small_file_chunks_1)
TEST(write_small_files_chunks_1)
{
char name[32];
int f;
int size = 512;
int files = ((20*(FS)->cfg.phys_size)/100)/size;
int res;
for (f = 0; f < files; f++) {
sprintf(name, "smallfile%i", f);
res = test_create_and_write_file(name, size, 1);
TEST_CHECK(res >= 0);
}
for (f = 0; f < files; f++) {
sprintf(name, "smallfile%i", f);
res = read_and_verify(name);
TEST_CHECK(res >= 0);
}
return TEST_RES_OK;
}
TEST_END(write_small_files_chunks_1)
TEST(write_big_file_chunks_1)
{
int size = ((50*(FS)->cfg.phys_size)/100);
printf(" filesize %i\n", size);
int res = test_create_and_write_file("bigfile", size, 1);
TEST_CHECK(res >= 0);
res = read_and_verify("bigfile");
TEST_CHECK(res >= 0);
return TEST_RES_OK;
}
TEST_END(write_big_file_chunks_1)
TEST(write_big_files_chunks_1)
{
char name[32];
int f;
int files = 10;
int res;
int size = ((50*(FS)->cfg.phys_size)/100)/files;
printf(" filesize %i\n", size);
for (f = 0; f < files; f++) {
sprintf(name, "bigfile%i", f);
res = test_create_and_write_file(name, size, 1);
TEST_CHECK(res >= 0);
}
for (f = 0; f < files; f++) {
sprintf(name, "bigfile%i", f);
res = read_and_verify(name);
TEST_CHECK(res >= 0);
}
return TEST_RES_OK;
}
TEST_END(write_big_files_chunks_1)
TEST(long_run_config_many_small_one_long)
{
tfile_conf cfgs[] = {
{ .tsize = LARGE, .ttype = MODIFIED, .tlife = LONG
},
{ .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = LONG
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = LONG
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = LONG
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = LONG
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = LONG
},
};
int res = run_file_config(sizeof(cfgs)/sizeof(cfgs[0]), &cfgs[0], 206, 5, 0);
TEST_CHECK(res >= 0);
return TEST_RES_OK;
}
TEST_END(long_run_config_many_small_one_long)
TEST(long_run_config_many_medium)
{
tfile_conf cfgs[] = {
{ .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG
},
{ .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG
},
{ .tsize = LARGE, .ttype = MODIFIED, .tlife = LONG
},
{ .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG
},
{ .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG
},
{ .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG
},
{ .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG
},
{ .tsize = LARGE, .ttype = MODIFIED, .tlife = LONG
},
{ .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG
},
{ .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG
},
{ .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG
},
{ .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG
},
{ .tsize = LARGE, .ttype = MODIFIED, .tlife = LONG
},
{ .tsize = MEDIUM, .ttype = APPENDED, .tlife = LONG
},
{ .tsize = MEDIUM, .ttype = MODIFIED, .tlife = LONG
},
};
int res = run_file_config(sizeof(cfgs)/sizeof(cfgs[0]), &cfgs[0], 305, 5, 0);
TEST_CHECK(res >= 0);
return TEST_RES_OK;
}
TEST_END(long_run_config_many_medium)
TEST(long_run_config_many_small)
{
tfile_conf cfgs[] = {
{ .tsize = SMALL, .ttype = APPENDED, .tlife = LONG
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = APPENDED, .tlife = LONG
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = MODIFIED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT
},
{ .tsize = SMALL, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = UNTAMPERED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = SHORT
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = MEDIUM
},
{ .tsize = EMPTY, .ttype = UNTAMPERED, .tlife = SHORT
},
};
int res = run_file_config(sizeof(cfgs)/sizeof(cfgs[0]), &cfgs[0], 115, 6, 0);
TEST_CHECK(res >= 0);
return TEST_RES_OK;
}
TEST_END(long_run_config_many_small)
TEST(long_run)
{
tfile_conf cfgs[] = {
{ .tsize = EMPTY, .ttype = APPENDED, .tlife = MEDIUM
},
{ .tsize = SMALL, .ttype = REWRITTEN, .tlife = SHORT
},
{ .tsize = MEDIUM, .ttype = MODIFIED, .tlife = SHORT
},
{ .tsize = MEDIUM, .ttype = APPENDED, .tlife = SHORT
},
};
int macro_runs = 500;
printf(" ");
u32_t clob_size = SPIFFS_CFG_PHYS_SZ(FS)/4;
int res = test_create_and_write_file("long_clobber", clob_size, clob_size);
TEST_CHECK(res >= 0);
res = read_and_verify("long_clobber");
TEST_CHECK(res >= 0);
while (macro_runs--) {
//printf(" ---- run %i ----\n", macro_runs);
if ((macro_runs % 20) == 0) {
printf(".");
fflush(stdout);
}
res = run_file_config(sizeof(cfgs)/sizeof(cfgs[0]), &cfgs[0], 11, 2, 0);
TEST_CHECK(res >= 0);
}
printf("\n");
res = read_and_verify("long_clobber");
TEST_CHECK(res >= 0);
res = SPIFFS_check(FS);
TEST_CHECK(res >= 0);
return TEST_RES_OK;
}
TEST_END(long_run)
SUITE_END(hydrogen_tests)
/*
* test_spiffs.c
*
* Created on: Jun 19, 2013
* Author: petera
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "params_test.h"
#include "spiffs.h"
#include "spiffs_nucleus.h"
#include "testrunner.h"
#include "test_spiffs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
static unsigned char area[PHYS_FLASH_SIZE];
static int erases[256];
static char _path[256];
static u32_t bytes_rd = 0;
static u32_t bytes_wr = 0;
static u32_t reads = 0;
static u32_t writes = 0;
static u32_t error_after_bytes_written = 0;
static u32_t error_after_bytes_read = 0;
static char error_after_bytes_written_once_only = 0;
static char error_after_bytes_read_once_only = 0;
static char log_flash_ops = 1;
static u32_t fs_check_fixes = 0;
spiffs __fs;
static u8_t _work[LOG_PAGE*2];
static u8_t _fds[FD_BUF_SIZE];
static u8_t _cache[CACHE_BUF_SIZE];
static int check_valid_flash = 1;
#define TEST_PATH "test_data/"
char *make_test_fname(const char *name) {
sprintf(_path, "%s%s", TEST_PATH, name);
return _path;
}
void clear_test_path() {
DIR *dp;
struct dirent *ep;
dp = opendir(TEST_PATH);
if (dp != NULL) {
while ((ep = readdir(dp))) {
if (ep->d_name[0] != '.') {
sprintf(_path, "%s%s", TEST_PATH, ep->d_name);
remove(_path);
}
}
closedir(dp);
}
}
static s32_t _read(u32_t addr, u32_t size, u8_t *dst) {
if (log_flash_ops) {
bytes_rd += size;
reads++;
if (error_after_bytes_read > 0 && bytes_rd >= error_after_bytes_read) {
if (error_after_bytes_read_once_only) {
error_after_bytes_read = 0;
}
return SPIFFS_ERR_TEST;
}
}
if (addr < __fs.cfg.phys_addr) {
printf("FATAL read addr too low %08x < %08x\n", addr, SPIFFS_PHYS_ADDR);
exit(0);
}
if (addr + size > __fs.cfg.phys_addr + __fs.cfg.phys_size) {
printf("FATAL read addr too high %08x + %08x > %08x\n", addr, size, SPIFFS_PHYS_ADDR + SPIFFS_FLASH_SIZE);
exit(0);
}
memcpy(dst, &area[addr], size);
return 0;
}
static s32_t _write(u32_t addr, u32_t size, u8_t *src) {
int i;
//printf("wr %08x %i\n", addr, size);
if (log_flash_ops) {
bytes_wr += size;
writes++;
if (error_after_bytes_written > 0 && bytes_wr >= error_after_bytes_written) {
if (error_after_bytes_written_once_only) {
error_after_bytes_written = 0;
}
return SPIFFS_ERR_TEST;
}
}
if (addr < __fs.cfg.phys_addr) {
printf("FATAL write addr too low %08x < %08x\n", addr, SPIFFS_PHYS_ADDR);
exit(0);
}
if (addr + size > __fs.cfg.phys_addr + __fs.cfg.phys_size) {
printf("FATAL write addr too high %08x + %08x > %08x\n", addr, size, SPIFFS_PHYS_ADDR + SPIFFS_FLASH_SIZE);
exit(0);
}
for (i = 0; i < size; i++) {
if (((addr + i) & (__fs.cfg.log_page_size-1)) != offsetof(spiffs_page_header, flags)) {
if (check_valid_flash && ((area[addr + i] ^ src[i]) & src[i])) {
printf("trying to write %02x to %02x at addr %08x\n", src[i], area[addr + i], addr+i);
spiffs_page_ix pix = (addr + i) / LOG_PAGE;
dump_page(&__fs, pix);
return -1;
}
}
area[addr + i] &= src[i];
}
return 0;
}
static s32_t _erase(u32_t addr, u32_t size) {
if (addr & (__fs.cfg.phys_erase_block-1)) {
printf("trying to erase at addr %08x, out of boundary\n", addr);
return -1;
}
if (size & (__fs.cfg.phys_erase_block-1)) {
printf("trying to erase at with size %08x, out of boundary\n", size);
return -1;
}
erases[(addr-__fs.cfg.phys_addr)/__fs.cfg.phys_erase_block]++;
memset(&area[addr], 0xff, size);
return 0;
}
void hexdump_mem(u8_t *b, u32_t len) {
while (len--) {
if ((((intptr_t)b)&0x1f) == 0) {
printf("\n");
}
printf("%02x", *b++);
}
printf("\n");
}
void hexdump(u32_t addr, u32_t len) {
int remainder = (addr % 32) == 0 ? 0 : 32 - (addr % 32);
u32_t a;
for (a = addr - remainder; a < addr+len; a++) {
if ((a & 0x1f) == 0) {
if (a != addr) {
printf(" ");
int j;
for (j = 0; j < 32; j++) {
if (a-32+j < addr)
printf(" ");
else {
printf("%c", (area[a-32+j] < 32 || area[a-32+j] >= 0x7f) ? '.' : area[a-32+j]);
}
}
}
printf("%s %08x: ", a<=addr ? "":"\n", a);
}
if (a < addr) {
printf(" ");
} else {
printf("%02x", area[a]);
}
}
int j;
printf(" ");
for (j = 0; j < 32; j++) {
if (a-32+j < addr)
printf(" ");
else {
printf("%c", (area[a-32+j] < 32 || area[a-32+j] >= 0x7f) ? '.' : area[a-32+j]);
}
}
printf("\n");
}
void dump_page(spiffs *fs, spiffs_page_ix p) {
printf("page %04x ", p);
u32_t addr = SPIFFS_PAGE_TO_PADDR(fs, p);
if (p % SPIFFS_PAGES_PER_BLOCK(fs) < SPIFFS_OBJ_LOOKUP_PAGES(fs)) {
// obj lu page
printf("OBJ_LU");
} else {
u32_t obj_id_addr = SPIFFS_BLOCK_TO_PADDR(fs, SPIFFS_BLOCK_FOR_PAGE(fs , p)) +
SPIFFS_OBJ_LOOKUP_ENTRY_FOR_PAGE(fs, p) * sizeof(spiffs_obj_id);
spiffs_obj_id obj_id = *((spiffs_obj_id *)&area[obj_id_addr]);
// data page
spiffs_page_header *ph = (spiffs_page_header *)&area[addr];
printf("DATA %04x:%04x ", obj_id, ph->span_ix);
printf("%s", ((ph->flags & SPIFFS_PH_FLAG_FINAL) == 0) ? "FIN " : "fin ");
printf("%s", ((ph->flags & SPIFFS_PH_FLAG_DELET) == 0) ? "DEL " : "del ");
printf("%s", ((ph->flags & SPIFFS_PH_FLAG_INDEX) == 0) ? "IDX " : "idx ");
printf("%s", ((ph->flags & SPIFFS_PH_FLAG_USED) == 0) ? "USD " : "usd ");
printf("%s ", ((ph->flags & SPIFFS_PH_FLAG_IXDELE) == 0) ? "IDL " : "idl ");
if (obj_id & SPIFFS_OBJ_ID_IX_FLAG) {
// object index
printf("OBJ_IX");
if (ph->span_ix == 0) {
printf("_HDR ");
spiffs_page_object_ix_header *oix_hdr = (spiffs_page_object_ix_header *)&area[addr];
printf("'%s' %i bytes type:%02x", oix_hdr->name, oix_hdr->size, oix_hdr->type);
}
} else {
// data page
printf("CONTENT");
}
}
printf("\n");
u32_t len = fs->cfg.log_page_size;
hexdump(addr, len);
}
void area_write(u32_t addr, u8_t *buf, u32_t size) {
int i;
for (i = 0; i < size; i++) {
area[addr + i] = *buf++;
}
}
void area_read(u32_t addr, u8_t *buf, u32_t size) {
int i;
for (i = 0; i < size; i++) {
*buf++ = area[addr + i];
}
}
void dump_erase_counts(spiffs *fs) {
spiffs_block_ix bix;
printf(" BLOCK |\n");
printf(" AGE COUNT|\n");
for (bix = 0; bix < fs->block_count; bix++) {
printf("----%3i ----|", bix);
}
printf("\n");
for (bix = 0; bix < fs->block_count; bix++) {
spiffs_obj_id erase_mark;
_spiffs_rd(fs, 0, 0, SPIFFS_ERASE_COUNT_PADDR(fs, bix), sizeof(spiffs_obj_id), (u8_t *)&erase_mark);
if (erases[bix] == 0) {
printf(" |");
} else {
printf("%7i %4i|", (fs->max_erase_count - erase_mark), erases[bix]);
}
}
printf("\n");
}
void dump_flash_access_stats() {
printf(" RD: %10i reads %10i bytes %10i avg bytes/read\n", reads, bytes_rd, reads == 0 ? 0 : (bytes_rd / reads));
printf(" WR: %10i writes %10i bytes %10i avg bytes/write\n", writes, bytes_wr, writes == 0 ? 0 : (bytes_wr / writes));
}
static u32_t old_perc = 999;
static void spiffs_check_cb_f(spiffs_check_type type, spiffs_check_report report,
u32_t arg1, u32_t arg2) {
/* if (report == SPIFFS_CHECK_PROGRESS && old_perc != arg1) {
old_perc = arg1;
printf("CHECK REPORT: ");
switch(type) {
case SPIFFS_CHECK_LOOKUP:
printf("LU "); break;
case SPIFFS_CHECK_INDEX:
printf("IX "); break;
case SPIFFS_CHECK_PAGE:
printf("PA "); break;
}
printf("%i%%\n", arg1 * 100 / 256);
}*/
if (report != SPIFFS_CHECK_PROGRESS) {
if (report != SPIFFS_CHECK_ERROR) fs_check_fixes++;
printf(" check: ");
switch (type) {
case SPIFFS_CHECK_INDEX:
printf("INDEX "); break;
case SPIFFS_CHECK_LOOKUP:
printf("LOOKUP "); break;
case SPIFFS_CHECK_PAGE:
printf("PAGE "); break;
default:
printf("???? "); break;
}
if (report == SPIFFS_CHECK_ERROR) {
printf("ERROR %i", arg1);
} else if (report == SPIFFS_CHECK_DELETE_BAD_FILE) {
printf("DELETE BAD FILE %04x", arg1);
} else if (report == SPIFFS_CHECK_DELETE_ORPHANED_INDEX) {
printf("DELETE ORPHANED INDEX %04x", arg1);
} else if (report == SPIFFS_CHECK_DELETE_PAGE) {
printf("DELETE PAGE %04x", arg1);
} else if (report == SPIFFS_CHECK_FIX_INDEX) {
printf("FIX INDEX %04x:%04x", arg1, arg2);
} else if (report == SPIFFS_CHECK_FIX_LOOKUP) {
printf("FIX INDEX %04x:%04x", arg1, arg2);
} else {
printf("??");
}
printf("\n");
}
}
void fs_reset_specific(u32_t phys_addr, u32_t phys_size,
u32_t phys_sector_size,
u32_t log_block_size, u32_t log_page_size) {
memset(area, 0xcc, sizeof(area));
memset(&area[phys_addr], 0xff, phys_size);
spiffs_config c;
c.hal_erase_f = _erase;
c.hal_read_f = _read;
c.hal_write_f = _write;
c.log_block_size = log_block_size;
c.log_page_size = log_page_size;
c.phys_addr = phys_addr;
c.phys_erase_block = phys_sector_size;
c.phys_size = phys_size;
memset(erases,0,sizeof(erases));
memset(_cache,0,sizeof(_cache));
SPIFFS_mount(&__fs, &c, _work, _fds, sizeof(_fds), _cache, sizeof(_cache), spiffs_check_cb_f);
clear_flash_ops_log();
log_flash_ops = 1;
fs_check_fixes = 0;
}
void fs_reset() {
fs_reset_specific(SPIFFS_PHYS_ADDR, SPIFFS_FLASH_SIZE, SECTOR_SIZE, LOG_BLOCK, LOG_PAGE);
}
void set_flash_ops_log(int enable) {
log_flash_ops = enable;
}
void clear_flash_ops_log() {
bytes_rd = 0;
bytes_wr = 0;
reads = 0;
writes = 0;
error_after_bytes_read = 0;
error_after_bytes_written = 0;
}
u32_t get_flash_ops_log_read_bytes() {
return bytes_rd;
}
u32_t get_flash_ops_log_write_bytes() {
return bytes_wr;
}
void invoke_error_after_read_bytes(u32_t b, char once_only) {
error_after_bytes_read = b;
error_after_bytes_read_once_only = once_only;
}
void invoke_error_after_write_bytes(u32_t b, char once_only) {
error_after_bytes_written = b;
error_after_bytes_written_once_only = once_only;
}
void fs_set_validate_flashing(int i) {
check_valid_flash = i;
}
void real_assert(int c, const char *n, const char *file, int l) {
if (c == 0) {
printf("ASSERT: %s %s @ %i\n", (n ? n : ""), file, l);
printf("fs errno:%i\n", __fs.err_code);
exit(0);
}
}
int read_and_verify(char *name) {
s32_t res;
int fd = SPIFFS_open(&__fs, name, SPIFFS_RDONLY, 0);
if (fd < 0) {
printf(" read_and_verify: could not open file %s\n", name);
return fd;
}
return read_and_verify_fd(fd, name);
}
int read_and_verify_fd(spiffs_file fd, char *name) {
s32_t res;
int pfd = open(make_test_fname(name), O_RDONLY);
spiffs_stat s;
res = SPIFFS_fstat(&__fs, fd, &s);
if (res < 0) {
printf(" read_and_verify: could not stat file %s\n", name);
return res;
}
if (s.size == 0) {
SPIFFS_close(&__fs, fd);
close(pfd);
return 0;
}
//printf("verifying %s, len %i\n", name, s.size);
int offs = 0;
u8_t buf_d[256];
u8_t buf_v[256];
while (offs < s.size) {
int read_len = MIN(s.size - offs, sizeof(buf_d));
res = SPIFFS_read(&__fs, fd, buf_d, read_len);
if (res < 0) {
printf(" read_and_verify: could not read file %s offs:%i len:%i filelen:%i\n", name, offs, read_len, s.size);
return res;
}
int pres = read(pfd, buf_v, read_len);
(void)pres;
//printf("reading offs:%i len:%i spiffs_res:%i posix_res:%i\n", offs, read_len, res, pres);
int i;
int veri_ok = 1;
for (i = 0; veri_ok && i < read_len; i++) {
if (buf_d[i] != buf_v[i]) {
printf("file verification mismatch @ %i, %02x %c != %02x %c\n", offs+i, buf_d[i], buf_d[i], buf_v[i], buf_v[i]);
int j = MAX(0, i-16);
int k = MIN(sizeof(buf_d), i+16);
k = MIN(s.size-offs, k);
int l;
for (l = j; l < k; l++) {
printf("%c", buf_d[l] > 31 ? buf_d[l] : '.');
}
printf("\n");
for (l = j; l < k; l++) {
printf("%c", buf_v[l] > 31 ? buf_v[l] : '.');
}
printf("\n");
veri_ok = 0;
}
}
if (!veri_ok) {
SPIFFS_close(&__fs, fd);
close(pfd);
printf("data mismatch\n");
return -1;
}
offs += read_len;
}
SPIFFS_close(&__fs, fd);
close(pfd);
return 0;
}
static void test_on_stop(test *t) {
printf(" spiffs errno:%i\n", SPIFFS_errno(&__fs));
#if SPIFFS_TEST_VISUALISATION
SPIFFS_vis(FS);
#endif
}
void memrand(u8_t *b, int len) {
int i;
for (i = 0; i < len; i++) {
b[i] = rand();
}
}
int test_create_file(char *name) {
spiffs_stat s;
spiffs_file fd;
int res = SPIFFS_creat(FS, name, 0);
CHECK_RES(res);
fd = SPIFFS_open(FS, name, SPIFFS_RDONLY, 0);
CHECK(fd >= 0);
res = SPIFFS_fstat(FS, fd, &s);
CHECK_RES(res);
CHECK(strcmp((char*)s.name, name) == 0);
CHECK(s.size == 0);
SPIFFS_close(FS, fd);
return 0;
}
int test_create_and_write_file(char *name, int size, int chunk_size) {
int res;
spiffs_file fd;
printf(" create and write %s", name);
res = test_create_file(name);
if (res < 0) {
printf(" failed creation, %i\n",res);
}
CHECK(res >= 0);
fd = SPIFFS_open(FS, name, SPIFFS_APPEND | SPIFFS_RDWR, 0);
if (res < 0) {
printf(" failed open, %i\n",res);
}
CHECK(fd >= 0);
int pfd = open(make_test_fname(name), O_APPEND | O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
int offset = 0;
int mark = 0;
while (offset < size) {
int len = MIN(size-offset, chunk_size);
if (offset > mark) {
mark += size/16;
printf(".");
fflush(stdout);
}
u8_t *buf = malloc(len);
memrand(buf, len);
res = SPIFFS_write(FS, fd, buf, len);
write(pfd, buf, len);
free(buf);
if (res < 0) {
printf("\n error @ offset %i, res %i\n", offset, res);
}
offset += len;
CHECK(res >= 0);
}
printf("\n");
close(pfd);
spiffs_stat stat;
res = SPIFFS_fstat(FS, fd, &stat);
if (res < 0) {
printf(" failed fstat, %i\n",res);
}
CHECK(res >= 0);
if (stat.size != size) {
printf(" failed size, %i != %i\n", stat.size, size);
}
CHECK(stat.size == size);
SPIFFS_close(FS, fd);
return 0;
}
#if SPIFFS_CACHE
#if SPIFFS_CACHE_STATS
static u32_t chits_tot = 0;
static u32_t cmiss_tot = 0;
#endif
#endif
void _setup_test_only() {
fs_set_validate_flashing(1);
test_init(test_on_stop);
}
void _setup() {
fs_reset();
_setup_test_only();
}
void _teardown() {
printf(" free blocks : %i of %i\n", (FS)->free_blocks, (FS)->block_count);
printf(" pages allocated : %i\n", (FS)->stats_p_allocated);
printf(" pages deleted : %i\n", (FS)->stats_p_deleted);
#if SPIFFS_GC_STATS
printf(" gc runs : %i\n", (FS)->stats_gc_runs);
#endif
#if SPIFFS_CACHE
#if SPIFFS_CACHE_STATS
chits_tot += (FS)->cache_hits;
cmiss_tot += (FS)->cache_misses;
printf(" cache hits : %i (sum %i)\n", (FS)->cache_hits, chits_tot);
printf(" cache misses : %i (sum %i)\n", (FS)->cache_misses, cmiss_tot);
printf(" cache utiliz : %f\n", ((float)chits_tot/(float)(chits_tot + cmiss_tot)));
#endif
#endif
dump_flash_access_stats();
clear_flash_ops_log();
#if SPIFFS_GC_STATS
if ((FS)->stats_gc_runs > 0)
#endif
dump_erase_counts(FS);
printf(" fs consistency check:\n");
SPIFFS_check(FS);
clear_test_path();
//hexdump_mem(&area[SPIFFS_PHYS_ADDR - 16], 32);
//hexdump_mem(&area[SPIFFS_PHYS_ADDR + SPIFFS_FLASH_SIZE - 16], 32);
}
u32_t tfile_get_size(tfile_size s) {
switch (s) {
case EMPTY:
return 0;
case SMALL:
return SPIFFS_DATA_PAGE_SIZE(FS)/2;
case MEDIUM:
return SPIFFS_DATA_PAGE_SIZE(FS) * (SPIFFS_PAGES_PER_BLOCK(FS) - SPIFFS_OBJ_LOOKUP_PAGES(FS));
case LARGE:
return (FS)->cfg.phys_size/3;
}
return 0;
}
int run_file_config(int cfg_count, tfile_conf* cfgs, int max_runs, int max_concurrent_files, int dbg) {
int res;
tfile *tfiles = malloc(sizeof(tfile) * max_concurrent_files);
memset(tfiles, 0, sizeof(tfile) * max_concurrent_files);
int run = 0;
int cur_config_ix = 0;
char name[32];
while (run < max_runs) {
if (dbg) printf(" run %i/%i\n", run, max_runs);
int i;
for (i = 0; i < max_concurrent_files; i++) {
sprintf(name, "file%i_%i", (1+run), i);
tfile *tf = &tfiles[i];
if (tf->state == 0 && cur_config_ix < cfg_count) {
// create a new file
strcpy(tf->name, name);
tf->state = 1;
tf->cfg = cfgs[cur_config_ix];
int size = tfile_get_size(tf->cfg.tsize);
if (dbg) printf(" create new %s with cfg %i/%i, size %i\n", name, (1+cur_config_ix), cfg_count, size);
if (tf->cfg.tsize == EMPTY) {
res = SPIFFS_creat(FS, name, 0);
CHECK_RES(res);
int pfd = open(make_test_fname(name), O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
close(pfd);
int extra_flags = tf->cfg.ttype == APPENDED ? SPIFFS_APPEND : 0;
spiffs_file fd = SPIFFS_open(FS, name, extra_flags | SPIFFS_RDWR, 0);
CHECK(fd > 0);
tf->fd = fd;
} else {
int extra_flags = tf->cfg.ttype == APPENDED ? SPIFFS_APPEND : 0;
spiffs_file fd = SPIFFS_open(FS, name, extra_flags | SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR, 0);
CHECK(fd > 0);
extra_flags = tf->cfg.ttype == APPENDED ? O_APPEND : 0;
int pfd = open(make_test_fname(name), extra_flags | O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
tf->fd = fd;
u8_t *buf = malloc(size);
memrand(buf, size);
res = SPIFFS_write(FS, fd, buf, size);
CHECK_RES(res);
write(pfd, buf, size);
close(pfd);
free(buf);
res = read_and_verify(name);
CHECK_RES(res);
}
cur_config_ix++;
} else if (tf->state > 0) {
// hande file lifecycle
switch (tf->cfg.ttype) {
case UNTAMPERED: {
break;
}
case APPENDED: {
if (dbg) printf(" appending %s\n", tf->name);
int size = SPIFFS_DATA_PAGE_SIZE(FS)*3;
u8_t *buf = malloc(size);
memrand(buf, size);
res = SPIFFS_write(FS, tf->fd, buf, size);
CHECK_RES(res);
int pfd = open(make_test_fname(tf->name), O_APPEND | O_RDWR);
write(pfd, buf, size);
close(pfd);
free(buf);
res = read_and_verify(tf->name);
CHECK_RES(res);
break;
}
case MODIFIED: {
if (dbg) printf(" modify %s\n", tf->name);
spiffs_stat stat;
res = SPIFFS_fstat(FS, tf->fd, &stat);
CHECK_RES(res);
int size = stat.size / tf->cfg.tlife + SPIFFS_DATA_PAGE_SIZE(FS)/3;
int offs = (stat.size / tf->cfg.tlife) * tf->state;
res = SPIFFS_lseek(FS, tf->fd, offs, SPIFFS_SEEK_SET);
CHECK_RES(res);
u8_t *buf = malloc(size);
memrand(buf, size);
res = SPIFFS_write(FS, tf->fd, buf, size);
CHECK_RES(res);
int pfd = open(make_test_fname(tf->name), O_RDWR);
lseek(pfd, offs, SEEK_SET);
write(pfd, buf, size);
close(pfd);
free(buf);
res = read_and_verify(tf->name);
CHECK_RES(res);
break;
}
case REWRITTEN: {
if (tf->fd > 0) {
SPIFFS_close(FS, tf->fd);
}
if (dbg) printf(" rewriting %s\n", tf->name);
spiffs_file fd = SPIFFS_open(FS, tf->name, SPIFFS_TRUNC | SPIFFS_CREAT | SPIFFS_RDWR, 0);
CHECK(fd > 0);
int pfd = open(make_test_fname(tf->name), O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
tf->fd = fd;
int size = tfile_get_size(tf->cfg.tsize);
u8_t *buf = malloc(size);
memrand(buf, size);
res = SPIFFS_write(FS, fd, buf, size);
CHECK_RES(res);
write(pfd, buf, size);
close(pfd);
free(buf);
res = read_and_verify(tf->name);
CHECK_RES(res);
break;
}
}
tf->state++;
if (tf->state > tf->cfg.tlife) {
// file outlived its time, kill it
if (tf->fd > 0) {
SPIFFS_close(FS, tf->fd);
}
if (dbg) printf(" removing %s\n", tf->name);
res = read_and_verify(tf->name);
CHECK_RES(res);
res = SPIFFS_remove(FS, tf->name);
CHECK_RES(res);
remove(make_test_fname(tf->name));
memset(tf, 0, sizeof(tf));
}
}
}
run++;
}
free(tfiles);
return 0;
}
/*
* test_spiffs.h
*
* Created on: Jun 19, 2013
* Author: petera
*/
#ifndef TEST_SPIFFS_H_
#define TEST_SPIFFS_H_
#include "spiffs.h"
#define FS &__fs
extern spiffs __fs;
#define CHECK(r) if (!(r)) return -1;
#define CHECK_RES(r) if (r < 0) return -1;
#define FS_PURE_DATA_PAGES(fs) \
((fs)->cfg.phys_size / (fs)->cfg.log_page_size - (fs)->block_count * SPIFFS_OBJ_LOOKUP_PAGES(fs))
#define FS_PURE_DATA_SIZE(fs) \
FS_PURE_DATA_PAGES(fs) * SPIFFS_DATA_PAGE_SIZE(fs)
typedef enum {
EMPTY,
SMALL,
MEDIUM,
LARGE,
} tfile_size;
typedef enum {
UNTAMPERED,
APPENDED,
MODIFIED,
REWRITTEN,
} tfile_type;
typedef enum {
SHORT = 4,
NORMAL = 20,
LONG = 100,
} tfile_life;
typedef struct {
tfile_size tsize;
tfile_type ttype;
tfile_life tlife;
} tfile_conf;
typedef struct {
int state;
spiffs_file fd;
tfile_conf cfg;
char name[32];
} tfile;
void fs_reset();
void fs_reset_specific(u32_t phys_addr, u32_t phys_size,
u32_t phys_sector_size,
u32_t log_block_size, u32_t log_page_size);
int read_and_verify(char *name);
int read_and_verify_fd(spiffs_file fd, char *name);
void dump_page(spiffs *fs, spiffs_page_ix p);
void hexdump(u32_t addr, u32_t len);
char *make_test_fname(const char *name);
void clear_test_path();
void area_write(u32_t addr, u8_t *buf, u32_t size);
void area_read(u32_t addr, u8_t *buf, u32_t size);
void dump_erase_counts(spiffs *fs);
void dump_flash_access_stats();
void set_flash_ops_log(int enable);
void clear_flash_ops_log();
u32_t get_flash_ops_log_read_bytes();
u32_t get_flash_ops_log_write_bytes();
void invoke_error_after_read_bytes(u32_t b, char once_only);
void invoke_error_after_write_bytes(u32_t b, char once_only);
void memrand(u8_t *b, int len);
int test_create_file(char *name);
int test_create_and_write_file(char *name, int size, int chunk_size);
void _setup();
void _setup_test_only();
void _teardown();
u32_t tfile_get_size(tfile_size s);
int run_file_config(int cfg_count, tfile_conf* cfgs, int max_runs, int max_concurrent_files, int dbg);
#endif /* TEST_SPIFFS_H_ */
/*
* testrunner.c
*
* Created on: Jun 18, 2013
* Author: petera
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <unistd.h>
#include "testrunner.h"
static struct {
test *tests;
test *_last_test;
int test_count;
void (*on_stop)(test *t);
test_res *failed;
test_res *failed_last;
test_res *stopped;
test_res *stopped_last;
FILE *spec;
} test_main;
void test_init(void (*on_stop)(test *t)) {
test_main.on_stop = on_stop;
}
static char check_spec(char *name) {
if (test_main.spec) {
fseek(test_main.spec, 0, SEEK_SET);
char *line = NULL;
size_t sz;
ssize_t read;
while ((read = getline(&line, &sz, test_main.spec)) != -1) {
if (strncmp(line, name, strlen(name)) == 0) {
free(line);
return 1;
}
}
free(line);
return 0;
} else {
return 1;
}
}
void add_test(test_f f, char *name, void (*setup)(test *t), void (*teardown)(test *t)) {
if (f == 0) return;
if (!check_spec(name)) return;
DBGT("adding test %s\n", name);
test *t = malloc(sizeof(test));
memset(t, 0, sizeof(test));
t->f = f;
strcpy(t->name, name);
t->setup = setup;
t->teardown = teardown;
if (test_main.tests == 0) {
test_main.tests = t;
} else {
test_main._last_test->_next = t;
}
test_main._last_test = t;
test_main.test_count++;
}
static void add_res(test *t, test_res **head, test_res **last) {
test_res *tr = malloc(sizeof(test_res));
memset(tr,0,sizeof(test_res));
strcpy(tr->name, t->name);
if (*head == 0) {
*head = tr;
} else {
(*last)->_next = tr;
}
*last = tr;
}
static void dump_res(test_res **head) {
test_res *tr = (*head);
while (tr) {
test_res *next_tr = tr->_next;
printf(" %s\n", tr->name);
free(tr);
tr = next_tr;
}
}
void run_tests(int argc, char **args) {
memset(&test_main, 0, sizeof(test_main));
if (argc > 1) {
printf("running tests from %s\n", args[1]);
FILE *fd = fopen(args[1], "r");
if (fd == NULL) {
printf("%s not found\n", args[1]);
exit(EXIT_FAILURE);
}
test_main.spec = fd;
}
DBGT("adding suites...\n");
add_suites();
DBGT("%i tests added\n", test_main.test_count);
if (test_main.spec) {
fclose(test_main.spec);
}
if (test_main.test_count == 0) {
printf("No tests to run\n");
return;
}
int fd_success = open("_tests_ok", O_APPEND | O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
int fd_bad = open("_tests_fail", O_APPEND | O_TRUNC | O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
DBGT("running tests...\n");
int ok = 0;
int failed = 0;
int stopped = 0;
test *cur_t = test_main.tests;
int i = 1;
while (cur_t) {
cur_t->setup(cur_t);
test *next_test = cur_t->_next;
DBGT("TEST %i/%i : running test %s\n", i, test_main.test_count, cur_t->name);
i++;
int res = cur_t->f(cur_t);
cur_t->teardown(cur_t);
int fd = res == TEST_RES_OK ? fd_success : fd_bad;
write(fd, cur_t->name, strlen(cur_t->name));
write(fd, "\n", 1);
switch (res) {
case TEST_RES_OK:
ok++;
printf(" .. ok\n");
break;
case TEST_RES_FAIL:
failed++;
printf(" .. FAILED\n");
if (test_main.on_stop) test_main.on_stop(cur_t);
add_res(cur_t, &test_main.failed, &test_main.failed_last);
break;
case TEST_RES_ASSERT:
stopped++;
printf(" .. ABORTED\n");
if (test_main.on_stop) test_main.on_stop(cur_t);
add_res(cur_t, &test_main.stopped, &test_main.stopped_last);
break;
}
free(cur_t);
cur_t = next_test;
}
close(fd_success);
close(fd_bad);
DBGT("ran %i tests\n", test_main.test_count);
printf("Test report, %i tests\n", test_main.test_count);
printf("%i succeeded\n", ok);
printf("%i failed\n", failed);
dump_res(&test_main.failed);
printf("%i stopped\n", stopped);
dump_res(&test_main.stopped);
if (ok < test_main.test_count) {
printf("\nFAILED\n");
} else {
printf("\nALL TESTS OK\n");
}
}
/*
* testrunner.h
*
* Created on: Jun 19, 2013
* Author: petera
*/
/*
SUITE(mysuite)
void setup(test *t) {}
void teardown(test *t) {}
TEST(mytest) {
printf("mytest runs now..\n");
return 0;
} TEST_END(mytest)
SUITE_END(mysuite)
SUITE(mysuite2)
void setup(test *t) {}
void teardown(test *t) {}
TEST(mytest2a) {
printf("mytest2a runs now..\n");
return 0;
} TEST_END(mytest2a)
TEST(mytest2b) {
printf("mytest2b runs now..\n");
return 0;
} TEST_END(mytest2b)
SUITE_END(mysuite2)
void add_suites() {
ADD_SUITE(mysuite);
ADD_SUITE(mysuite2);
}
*/
#ifndef TESTS_H_
#define TESTS_H_
#define TEST_RES_OK 0
#define TEST_RES_FAIL -1
#define TEST_RES_ASSERT -2
struct test_s;
typedef int (*test_f)(struct test_s *t);
typedef struct test_s {
test_f f;
char name[256];
void *data;
void (*setup)(struct test_s *t);
void (*teardown)(struct test_s *t);
struct test_s *_next;
} test;
typedef struct test_res_s {
char name[256];
struct test_res_s *_next;
} test_res;
#define TEST_CHECK(x) if (!(x)) { \
printf(" TEST FAIL %s:%i\n", __FILE__, __LINE__); \
goto __fail_stop; \
}
#define TEST_ASSERT(x) if (!(x)) { \
printf(" TEST ASSERT %s:%i\n", __FILE__, __LINE__); \
goto __fail_assert; \
}
#define DBGT(...) printf(__VA_ARGS__)
#define str(s) #s
#define SUITE(sui) \
extern void __suite_##sui() {
#define SUITE_END(sui) \
}
#define ADD_SUITE(sui) \
__suite_##sui();
#define TEST(tf) \
int tf(struct test_s *t) { do
#define TEST_END(tf) \
while(0); \
__fail_stop: return TEST_RES_FAIL; \
__fail_assert: return TEST_RES_ASSERT; \
} \
add_test(tf, str(tf), setup, teardown);
void add_suites();
void test_init(void (*on_stop)(test *t));
void add_test(test_f f, char *name, void (*setup)(test *t), void (*teardown)(test *t));
void run_tests(int argc, char **args);
#endif /* TESTS_H_ */
/*
* testsuites.c
*
* Created on: Jun 19, 2013
* Author: petera
*/
#include "testrunner.h"
void add_suites() {
//ADD_SUITE(dev_tests);
ADD_SUITE(check_tests);
ADD_SUITE(hydrogen_tests)
ADD_SUITE(bug_tests)
}
......@@ -90,16 +90,6 @@ void nodemcu_init(void)
// Fit hardware real flash size.
flash_rom_set_size_byte(flash_safe_get_size_byte());
if( !fs_format() )
{
NODE_ERR( "\ni*** ERROR ***: unable to format. FS might be compromised.\n" );
NODE_ERR( "It is advised to re-flash the NodeMCU image.\n" );
}
else{
NODE_ERR( "format done.\n" );
}
fs_unmount(); // mounted by format.
// Reboot to get SDK to use (or write) init data at new location
system_restart ();
......@@ -109,7 +99,15 @@ void nodemcu_init(void)
#endif // defined(FLASH_SAFE_API)
#if defined ( BUILD_SPIFFS )
fs_mount();
if (!fs_mount()) {
// Failed to mount -- try reformat
c_printf("Formatting file system. Please wait...\n");
if (!fs_format()) {
NODE_ERR( "\n*** ERROR ***: unable to format. FS might be compromised.\n" );
NODE_ERR( "It is advised to re-flash the NodeMCU image.\n" );
}
// Note that fs_format leaves the file system mounted
}
// test_spiffs();
#endif
// endpoint_setup();
......@@ -122,11 +120,56 @@ void nodemcu_init(void)
void user_rf_pre_init(void)
{
//set WiFi hostname before RF initialization (adds ~440 us to boot time)
//set WiFi hostname before RF initialization (adds ~479 us to boot time)
wifi_change_default_host_name();
}
#endif
/******************************************************************************
* FunctionName : user_rf_cal_sector_set
* Description : SDK just reversed 4 sectors, used for rf init data and paramters.
* We add this function to force users to set rf cal sector, since
* we don't know which sector is free in user's application.
* sector map for last several sectors : ABCCC
* A : rf cal
* B : rf init data
* C : sdk parameters
* Parameters : none
* Returns : rf cal sector
*******************************************************************************/
uint32
user_rf_cal_sector_set(void)
{
enum flash_size_map size_map = system_get_flash_size_map();
uint32 rf_cal_sec = 0;
switch (size_map) {
case FLASH_SIZE_4M_MAP_256_256:
rf_cal_sec = 128 - 5;
break;
case FLASH_SIZE_8M_MAP_512_512:
rf_cal_sec = 256 - 5;
break;
case FLASH_SIZE_16M_MAP_512_512:
case FLASH_SIZE_16M_MAP_1024_1024:
rf_cal_sec = 512 - 5;
break;
case FLASH_SIZE_32M_MAP_512_512:
case FLASH_SIZE_32M_MAP_1024_1024:
rf_cal_sec = 1024 - 5;
break;
default:
rf_cal_sec = 0;
break;
}
return rf_cal_sec;
}
/******************************************************************************
* FunctionName : user_init
* Description : entry of user application, init user function here
......
Adafruit provides a really nice [firmware flashing tutorial](https://learn.adafruit.com/building-and-running-micropython-on-the-esp8266/flash-firmware). Below you'll find just the basics for the two popular tools esptool and NodeMCU Flasher.
!!! note "Note:"
!!! attention
Keep in mind that the ESP8266 needs to be [put into flash mode](#putting-device-into-flash-mode) before you can flash a new firmware!
## esptool
## esptool.py
> A cute Python utility to communicate with the ROM bootloader in Espressif ESP8266. It is intended to be a simple, platform independent, open source replacement for XTCOM.
Source: [https://github.com/themadinventor/esptool](https://github.com/themadinventor/esptool)
......@@ -15,7 +15,7 @@ Supported platforms: OS X, Linux, Windows, anything that runs Python
Run the following command to flash an *aggregated* binary as is produced for example by the [cloud build service](build.md#cloud-build-service) or the [Docker image](build.md#docker-image).
`esptool.py --port <USB-port-with-ESP8266> write_flash -fm <mode> -fs <size> 0x00000 <nodemcu-firmware>.bin`
`esptool.py --port <serial-port-of-ESP8266> write_flash -fm <mode> -fs <size> 0x00000 <nodemcu-firmware>.bin`
- `mode` is `qio` for 512&nbsp;kByte modules and `dio` for 4&nbsp;MByte modules (`qio` might work as well, YMMV).
- `size` is given in bits. Specify `4m` for 512&nbsp;kByte and `32m` for 4&nbsp;MByte.
......@@ -48,18 +48,32 @@ Otherwise, if you built your own firmware from source code:
Also, in some special circumstances, you may need to flash `blank.bin` or `esp_init_data_default.bin` to various addresses on the flash (depending on flash size and type), see [below](#upgrading-from-sdk-09x-firmware).
If upgrading from [SPIFFS](https://github.com/pellepl/spiffs) version 0.3.2 to 0.3.3 or later, or after flashing any new firmware (particularly one with a much different size), you may need to run [`file.format()`](modules/file.md#fileformat) to re-format your flash filesystem. You will know if you need to do this if your flash files disappeared, or if they exist but seem empty, or if data cannot be written to new files.
## Upgrading Firmware
!!! important
It goes without saying that you shouldn't expect your NodeMCU 0.9.x Lua scripts to work error-free on a more recent firmware. Most notably Espressif changed the `socket:send` operation to be asynchronous i.e. non-blocking. See [API documentation](modules/net.md#netsocketsend) for details.
Espressif changes the init data block (`esp_init_data_default.bin`) for their devices along the way with the SDK. So things break when a NodeMCU firmware with a certain SDK is flashed to a module which contains init data from a different SDK. Hence, this section applies to upgrading NodeMCU firmware just as well as *downgrading* firmware.
A typical case that often fails is when a module is upgraded from a 0.9.x firmware to a recent version. It might look like the new firmware is broken, but the reason for the missing Lua prompt is related to the big jump in SDK versions.
If there is no init data block found during SDK startup, the SDK will install one itself. If there is a previous (potentially too old) init block, the SDK *probably* doesn't do anything with it but there is no documentation from Espressif on this topic.
Hence, there are two strategies to update the SDK init data:
- Erase flash completely. This will also erase the (Lua) files you uploaded to the device! The SDK will install the init data block during startup.
- Don't erase the flash but replace just the init data with a new file during the flashing procedure. For this you would download [SDK patch 1.5.4.1](http://bbs.espressif.com/download/file.php?id=1572) and extract `esp_init_data_default.bin` from there.
## Upgrading from SDK 0.9.x Firmware
If you flash a recent NodeMCU firmware for the first time, it's advisable that you get all accompanying files right. A typical case that often fails is when a module is upgraded from a 0.9.x firmware to the latest version built from the [NodeMCU build service](http://nodemcu-build.com). It might look like the brand new firmware is broken, but the reason for the missing Lua prompt is related to the big jump in SDK versions: Espressif changed the `esp_init_data_default.bin` for their devices along the way with the [SDK 1.4.0 release](http://bbs.espressif.com/viewtopic.php?f=46&t=1124). So things break when a NodeMCU firmware with SDK 1.4.0 or above is flashed to a module which contains old init data from a previous SDK.
When flashing a new firmware (particularly with a much different size), the flash filesystem may be reformatted as the firmware starts. If it is not automatically reformatted, then it should be valid and have the same contents as before the flash operation. You can still run [`file.format()`](modules/file.md#fileformat) to re-format your flash filesystem. You will know if you need to do this if your flash files exist but seem empty, or if data cannot be written to new files. However, this should be an exceptional case.
Download a recent SDK release, e.g. [esp_iot_sdk_v1.4.0_15_09_18.zip](http://bbs.espressif.com/download/file.php?id=838) or later and extract `esp_init_data_default.bin` from there. *Use this file together with the new firmware during flashing*.
**esptool.py**
**esptool**
For [esptool.py](https://github.com/themadinventor/esptool) you specify the init data file as an additional file for the `write_flash` command.
For [esptool](https://github.com/themadinventor/esptool) you specify another file to download at the command line.
```
esptool.py write_flash <flash options> 0x00000 <nodemcu-firmware>.bin 0x7c000 esp_init_data_default.bin
esptool.py --port <serial-port-of-ESP8266> erase_flash
esptool.py --port <serial-port-of-ESP8266> write_flash <flash options> 0x00000 <nodemcu-firmware>.bin <init-data-address> esp_init_data_default.bin
```
!!! note "Note:"
......
......@@ -11,11 +11,11 @@ The NodeMCU programming model is similar to that of [Node.js](https://en.wikiped
-- a simple HTTP server
srv = net.createServer(net.TCP)
srv:listen(80, function(conn)
conn:on("receive", function(conn, payload)
conn:on("receive", function(sck, payload)
print(payload)
conn:send("<h1> Hello, NodeMCU.</h1>")
sck:send("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n<h1> Hello, NodeMCU.</h1>")
end)
conn:on("sent", function(conn) conn:close() end)
conn:on("sent", function(sck) sck:close() end)
end)
```
```lua
......
......@@ -14,14 +14,14 @@ This FAQ does not aim to help you to learn to program or even how to program in
The NodeMCU firmware implements Lua 5.1 over the Espressif SDK for its ESP8266 SoC and the IoT modules based on this.
* The official lua.org **[Lua Language specification](http://www.lua.org/manual/5.1/manual.html)** gives a terse but complete language specification.
* Its [FAQ](http://www.lua.org/faq.html) provides information on Lua availability and licensing issues.
* The **[unofficial Lua FAQ](http://www.luafaq.org/)** provides a lot of useful Q and A content, and is extremely useful for those learning Lua as a second language.
* The [Lua User's Wiki](http://lua-users.org/wiki/) gives useful example source and relevant discussion. In particular, its [Lua Learning Lua](http://lua-users.org/wiki/Learning) section is a good place to start learning Lua.
* The best book to learn Lua is *Programming in Lua* by Roberto Ierusalimschy, one of the creators of Lua. It's first edition is available free [online](http://www.lua.org/pil/contents.html) . The second edition was aimed at Lua 5.1, but is out of print. The third edition is still in print and available in paperback. It contains a lot more material and clearly identifies Lua 5.1 vs Lua 5.2 differences. **This third edition is widely available for purchase and probably the best value for money**. References of the format [PiL **n.m**] refer to section **n.m** in this edition.
* The Espressif ESP8266 architecture is closed source, but the Espressif SDK itself is continually being updated so the best way to get the documentation for this is to [google Espressif IoT SDK Programming Guide](https://www.google.co.uk/search?q=Espressif+IoT+SDK+Programming+Guide) or to look at the Espressif [downloads forum](http://bbs.espressif.com/viewforum.php?f=5) .
* The **[NodeMCU documentation](http://www.NodeMCU.com/docs/)** is available online. However, please remember that the development team are based in China, and English is a second language, so the documentation needs expanding and be could improved with technical proofing.
* As with all Open Source projects the source for the NodeMCU firmware is openly available on the [GitHub NodeMCU-firmware](https://github.com/NodeMCU/NodeMCU-firmware) repository.
* The official lua.org **[Lua Language specification](http://www.lua.org/manual/5.1/manual.html)** gives a terse but complete language specification.
* Its [FAQ](http://www.lua.org/faq.html) provides information on Lua availability and licensing issues.
* The **[unofficial Lua FAQ](http://www.luafaq.org/)** provides a lot of useful Q and A content, and is extremely useful for those learning Lua as a second language.
* The [Lua User's Wiki](http://lua-users.org/wiki/) gives useful example source and relevant discussion. In particular, its [Lua Learning Lua](http://lua-users.org/wiki/Learning) section is a good place to start learning Lua.
* The best book to learn Lua is *Programming in Lua* by Roberto Ierusalimschy, one of the creators of Lua. It's first edition is available free [online](http://www.lua.org/pil/contents.html) . The second edition was aimed at Lua 5.1, but is out of print. The third edition is still in print and available in paperback. It contains a lot more material and clearly identifies Lua 5.1 vs Lua 5.2 differences. **This third edition is widely available for purchase and probably the best value for money**. References of the format [PiL **n.m**] refer to section **n.m** in this edition.
* The Espressif ESP8266 architecture is closed source, but the Espressif SDK itself is continually being updated so the best way to get the documentation for this is to [google Espressif IoT SDK Programming Guide](https://www.google.co.uk/search?q=Espressif+IoT+SDK+Programming+Guide) or to look at the Espressif [downloads forum](http://bbs.espressif.com/viewforum.php?f=5) .
* The **[NodeMCU documentation](http://www.NodeMCU.com/docs/)** is available online. However, please remember that the development team are based in China, and English is a second language, so the documentation needs expanding and be could improved with technical proofing.
* As with all Open Source projects the source for the NodeMCU firmware is openly available on the [GitHub NodeMCU-firmware](https://github.com/NodeMCU/NodeMCU-firmware) repository.
### How is NodeMCU Lua different to standard Lua?
......@@ -41,46 +41,47 @@ The NodeMCU libraries act as C wrappers around registered Lua callback functions
### How is coding for the ESP8266 the same as standard Lua?
* This is a fully featured Lua 5.1 implementation so all standard Lua language constructs and data types work.
* The main standard Lua libraries -- `core`, `coroutine`, `string` and `table` are implemented.
* This is a fully featured Lua 5.1 implementation so all standard Lua language constructs and data types work.
* The main standard Lua libraries -- `core`, `coroutine`, `string` and `table` are implemented.
### How is coding for the ESP8266 different to standard Lua?
* The ESP8266 use onchip RAM and offchip Flash memory connected using a dedicated SPI interface. Both of these are *very* limited (when compared to systems than most application programmer use). The SDK and the Lua firmware already use the majority of this resource: the later build versions keep adding useful functionality, and unfortunately at an increased RAM and Flash cost, so depending on the build version and the number of modules installed the runtime can have as little as 17KB RAM and 40KB Flash available at an application level. This Flash memory is formatted an made available as a **SPI Flash File System (SPIFFS)** through the `file` library.
* However, if you choose to use a custom build, for example one which uses integer arithmetic instead of floating point, and which omits libraries that aren't needed for your application, then this can help a lot doubling these available resources. (See Marcel Stör's excellent [custom build tool](http://frightanic.com/NodeMCU-custom-build/) that he discusses in [this forum topic](http://www.esp8266.com/viewtopic.php?f=23&t=3001)). Even so, those developers who are used to dealing in MB or GB of RAM and file systems can easily run out of these resources. Some of the techniques discussed below can go a long way to mitigate this issue.
* Current versions of the ESP8266 run the SDK over the native hardware so there is no underlying operating system to capture errors and to provide graceful failure modes, so system or application errors can easily "PANIC" the system causing it to reboot. Error handling has been kept simple to save on the limited code space, and this exacerbates this tendency. Running out of a system resource such as RAM will invariably cause a messy failure and system reboot.
* There is currently no `debug` library support. So you have to use 1980s-style "binary-chop" to locate errors and use print statement diagnostics though the systems UART interface. (This omission was largely because of the Flash memory footprint of this library, but there is no reason in principle why we couldn't make this library available in the near future as an custom build option).
* The LTR implementation means that you can't easily extend standard libraries as you can in normal Lua, so for example an attempt to define `function table.pack()` will cause a runtime error because you can't write to the global `table`. (Yes, there are standard sand-boxing techniques to achieve the same effect by using metatable based inheritance, but if you try to use this type of approach within a real application, then you will find that you run out of RAM before you implement anything useful.)
* There are standard libraries to provide access to the various hardware options supported by the hardware: WiFi, GPIO, One-wire, I²C, SPI, ADC, PWM, UART, etc.
* The runtime system runs in interactive-mode. In this mode it first executes any `init.lua` script. It then "listens" to the serial port for input Lua chunks, and executes them once syntactically complete. There is no `luac` or batch support, although automated embedded processing is normally achieved by setting up the necessary event triggers in the `init.lua` script.
* The various libraries (`net`, `tmr`, `wifi`, etc.) use the SDK callback mechanism to bind Lua processing to individual events (for example a timer alarm firing). Developers should make full use of these events to keep Lua execution sequences short. *If any individual task takes too long to execute then other queued tasks can time-out and bad things start to happen.*
* Non-Lua processing (e.g. network functions) will usually only take place once the current Lua chunk has completed execution. So any network calls should be viewed at an asynchronous request. A common coding mistake is to assume that they are synchronous, that is if two `socket:send()` are on consecutive lines in a Lua programme, then the first has completed by the time the second is executed. This is wrong. Each `socket:send()` request simply queues the send operation for dispatch. Neither will start to process until the Lua code has return to is calling C function. Stacking up such requests in a single Lua task function burns scarce RAM and can trigger a PANIC. This true for timer, network, and other callbacks. It is even the case for actions such as requesting a system restart, as can be seen by the following example:
* The ESP8266 use onchip RAM and offchip Flash memory connected using a dedicated SPI interface. Both of these are *very* limited (when compared to systems than most application programmer use). The SDK and the Lua firmware already use the majority of this resource: the later build versions keep adding useful functionality, and unfortunately at an increased RAM and Flash cost, so depending on the build version and the number of modules installed the runtime can have as little as 17KB RAM and 40KB Flash available at an application level. This Flash memory is formatted an made available as a **SPI Flash File System (SPIFFS)** through the `file` library.
* However, if you choose to use a custom build, for example one which uses integer arithmetic instead of floating point, and which omits libraries that aren't needed for your application, then this can help a lot doubling these available resources. (See Marcel Stör's excellent [custom build tool](http://nodemcu-build.com) that he discusses in [this forum topic](http://www.esp8266.com/viewtopic.php?f=23&t=3001)). Even so, those developers who are used to dealing in MB or GB of RAM and file systems can easily run out of these resources. Some of the techniques discussed below can go a long way to mitigate this issue.
* Current versions of the ESP8266 run the SDK over the native hardware so there is no underlying operating system to capture errors and to provide graceful failure modes, so system or application errors can easily "PANIC" the system causing it to reboot. Error handling has been kept simple to save on the limited code space, and this exacerbates this tendency. Running out of a system resource such as RAM will invariably cause a messy failure and system reboot.
* There is currently no `debug` library support. So you have to use 1980s-style "binary-chop" to locate errors and use print statement diagnostics though the systems UART interface. (This omission was largely because of the Flash memory footprint of this library, but there is no reason in principle why we couldn't make this library available in the near future as an custom build option).
* The LTR implementation means that you can't easily extend standard libraries as you can in normal Lua, so for example an attempt to define `function table.pack()` will cause a runtime error because you can't write to the global `table`. (Yes, there are standard sand-boxing techniques to achieve the same effect by using metatable based inheritance, but if you try to use this type of approach within a real application, then you will find that you run out of RAM before you implement anything useful.)
* There are standard libraries to provide access to the various hardware options supported by the hardware: WiFi, GPIO, One-wire, I²C, SPI, ADC, PWM, UART, etc.
* The runtime system runs in interactive-mode. In this mode it first executes any `init.lua` script. It then "listens" to the serial port for input Lua chunks, and executes them once syntactically complete. There is no `luac` or batch support, although automated embedded processing is normally achieved by setting up the necessary event triggers in the `init.lua` script.
* The various libraries (`net`, `tmr`, `wifi`, etc.) use the SDK callback mechanism to bind Lua processing to individual events (for example a timer alarm firing). Developers should make full use of these events to keep Lua execution sequences short. *If any individual task takes too long to execute then other queued tasks can time-out and bad things start to happen.*
* Non-Lua processing (e.g. network functions) will usually only take place once the current Lua chunk has completed execution. So any network calls should be viewed at an asynchronous request. A common coding mistake is to assume that they are synchronous, that is if two `socket:send()` are on consecutive lines in a Lua programme, then the first has completed by the time the second is executed. This is wrong. Each `socket:send()` request simply queues the send operation for dispatch. Neither will start to process until the Lua code has return to is calling C function. Stacking up such requests in a single Lua task function burns scarce RAM and can trigger a PANIC. This true for timer, network, and other callbacks. It is even the case for actions such as requesting a system restart, as can be seen by the following example:
```lua
node.restart(); for i = 1, 20 do print("not quite yet -- ",i); end
node.restart(); for i = 1, 20 do print("not quite yet -- ",i); end
```
* You therefore *have* to implement ESP8266 Lua applications using an event driven approach. You have to understand which SDK API requests schedule asynchronous processing, and which define event actions through Lua callbacks. Yes, such an event-driven approach makes it difficult to develop procedurally structured applications, but it is well suited to developing the sorts of application that you will typically want to implement on an IoT device.
* You therefore *have* to implement ESP8266 Lua applications using an event driven approach. You have to understand which SDK API requests schedule asynchronous processing, and which define event actions through Lua callbacks. Yes, such an event-driven approach makes it difficult to develop procedurally structured applications, but it is well suited to developing the sorts of application that you will typically want to implement on an IoT device.
### So how does the SDK event / tasking system work in Lua?
* The SDK employs an event-driven and task-oriented architecture for programming at an applications level.
* The SDK uses a startup hook `void user_init(void)`, defined by convention in the C module `user_main.c`, which it invokes on boot. The `user_init()` function can be used to do any initialisation required and to call the necessary timer alarms or other SDK API calls to bind and callback routines to implement the tasks needed in response to any system events.
* The API provides a set of functions for declaring application functions (written in C) as callbacks to associate application tasks with specific hardware and timer events. These are non-preemptive at an applications level.
* Whilst the SDK provides a number of interrupt driven device drivers, the hardware architecture severely limits the memory available for these drivers, so writing new device drivers is not a viable options for most developers
* The SDK interfaces internally with hardware and device drivers to queue pending events.
* The registered callback routines are invoked sequentially with the associated C task running to completion uninterrupted.
* In the case of Lua, these C tasks are typically functions within the Lua runtime library code and these typically act as C wrappers around the corresponding developer-provided Lua callback functions. An example here is the Lua `tmr.alarm(id, interval, repeat, callback)` function. The calls a function in the `tmr` library which registers a C function for this alarm using the SDK, and when this C function is called it then invokes the Lua callback.
* The SDK employs an event-driven and task-oriented architecture for programming at an applications level.
* The SDK uses a startup hook `void user_init(void)`, defined by convention in the C module `user_main.c`, which it invokes on boot. The `user_init()` function can be used to do any initialisation required and to call the necessary timer alarms or other SDK API calls to bind and callback routines to implement the tasks needed in response to any system events.
* The API provides a set of functions for declaring application functions (written in C) as callbacks to associate application tasks with specific hardware and timer events. These are non-preemptive at an applications level.
* Whilst the SDK provides a number of interrupt driven device drivers, the hardware architecture severely limits the memory available for these drivers, so writing new device drivers is not a viable options for most developers
* The SDK interfaces internally with hardware and device drivers to queue pending events.
* The registered callback routines are invoked sequentially with the associated C task running to completion uninterrupted.
* In the case of Lua, these C tasks are typically functions within the Lua runtime library code and these typically act as C wrappers around the corresponding developer-provided Lua callback functions. An example here is the Lua `tmr.alarm(id, interval, repeat, callback)` function. The calls a function in the `tmr` library which registers a C function for this alarm using the SDK, and when this C function is called it then invokes the Lua callback.
The NodeMCU firmware simply mirrors this structure at a Lua scripting level:
* A startup module `init.lua` is invoked on boot. This function module can be used to do any initialisation required and to call the necessary timer alarms or libary calls to bind and callback routines to implement the tasks needed in response to any system events.
* The Lua libraries provide a set of functions for declaring application functions (written in Lua) as callbacks (which are stored in the [Lua registry](#so-how-is-the-lua-registry-used-and-why-is-this-important)) to associate application tasks with specific hardware and timer events. These are non-preemptive at an applications level.
* The Lua libraries work in consort with the SDK to queue pending events and invoke any registered Lua callback routines, which then run to completion uninterrupted.
* Excessively long-running Lua functions can therefore cause other system functions and services to timeout, or allocate memory to buffer queued data, which can then trigger either the watchdog timer or memory exhaustion, both of which will ultimately cause the system to reboot.
* By default, the Lua runtime also 'listens' to UART 0, the serial port, in interactive mode and will execute any Lua commands input through this serial port.
* A startup module `init.lua` is invoked on boot. This function module can be used to do any initialisation required and to call the necessary timer alarms or library calls to bind and callback routines to implement the tasks needed in response to any system events.
* The Lua libraries provide a set of functions for declaring application functions (written in Lua) as callbacks (which are stored in the [Lua registry](#so-how-is-the-lua-registry-used-and-why-is-this-important)) to associate application tasks with specific hardware and timer events. These are non-preemptive at an applications level* The Lua libraries work in consort with the SDK to queue pending events and invoke any registered Lua callback routines, which then run to completion uninterrupted.
* Excessively long-running Lua functions can therefore cause other system functions and services to timeout, or allocate memory to buffer queued data, which can then trigger either the watchdog timer or memory exhaustion, both of which will ultimately cause the system to reboot.
* By default, the Lua runtime also 'listens' to UART 0, the serial port, in interactive mode and will execute any Lua commands input through this serial port.
This event-driven approach is very different to a conventional procedural implementation of Lua.
Consider a simple telnet example given in `examples/fragment.lua`:
Consider the following snippet:
```lua
s=net.createServer(net.TCP)
......@@ -112,12 +113,12 @@ This example defines five Lua functions:
`s`, `con_std` and `s_output` are global, and no [upvalues](#why-is-it-importance-to-understand-how-upvalues-are-implemented-when-programming-for-the-esp8266) are used. There is no "correct" order to define these in, but we could reorder this code for clarity (though doing this adds a few extra globals) and define these functions separately one another. However, let us consider how this is executed:
* The outer module is compiled including the four internal functions.
* `Main` is then assigning the created `net.createServer()` to the global `s`. The `connection listener` closure is created and bound to a temporary variable which is then passed to the `socket.listen()` as an argument. The routine then exits returning control to the firmware.
* When another computer connects to port 23, the listener handler retrieves the reference to then connection listener and calls it with the socket parameter. This function then binds the s_output closure to the global `s_output`, and registers this function with the `node.output` hook. Likewise the `on receive` and `on disconnection` are bound to temporary variables which are passed to the respective on handlers. We now have four Lua function registered in the Lua runtime libraries associated with four events. This routine then exits returning control to the firmware.
* When a record is received, the on receive handler within the net library retrieves the reference to the `on receive` Lua function and calls it passing it the record. This routine then passes this to the `node.input()` and exits returning control to the firmware.
* The `node.input` handler polls on an 80 mSec timer alarm. If a compete Lua chunk is available (either via the serial port or node input function), then it executes it and any output is then passed to the `note.output` handler. which calls `s_output` function. Any pending sends are then processed.
* This cycle repeats until the other computer disconnects, and `net` library disconnection handler then calls the Lua `on disconnect` handler. This Lua routine dereferences the connected socket and closes the `node.output` hook and exits returning control to the disconnect handler which garbage collects any associated sockets and registered on handlers.
* The outer module is compiled including the four internal functions.
* `Main` is then assigning the created `net.createServer()` to the global `s`. The `connection listener` closure is created and bound to a temporary variable which is then passed to the `socket.listen()` as an argument. The routine then exits returning control to the firmware.
* When another computer connects to port 23, the listener handler retrieves the reference to then connection listener and calls it with the socket parameter. This function then binds the s_output closure to the global `s_output`, and registers this function with the `node.output` hook. Likewise the `on receive` and `on disconnection` are bound to temporary variables which are passed to the respective on handlers. We now have four Lua function registered in the Lua runtime libraries associated with four events. This routine then exits returning control to the firmware.
* When a record is received, the on receive handler within the net library retrieves the reference to the `on receive` Lua function and calls it passing it the record. This routine then passes this to the `node.input()` and exits returning control to the firmware.
* The `node.input` handler polls on an 80 mSec timer alarm. If a compete Lua chunk is available (either via the serial port or node input function), then it executes it and any output is then passed to the `note.output` handler. which calls `s_output` function. Any pending sends are then processed.
* This cycle repeats until the other computer disconnects, and `net` library disconnection handler then calls the Lua `on disconnect` handler. This Lua routine dereferences the connected socket and closes the `node.output` hook and exits returning control to the disconnect handler which garbage collects any associated sockets and registered on handlers.
Whilst this is all going on, The SDK can (and often will) schedule other event tasks in between these Lua executions (e.g. to do the actual TCP stack processing). The longest individual Lua execution in this example is only 18 bytecode instructions (in the main routine).
......@@ -135,18 +136,18 @@ SDK Callbacks include:
| net.server | `sk:listen(port,[ip],function(socket))` |
| net | `sk:on(event, function(socket, [, data]))`, `sk:send(string, function(sent))`, `sk:dns(domain, function(socket,ip))` |
| gpio | `trig(pin, type, function(level))` |
| mqqt | `client:m:on(event, function(conn[, topic, data])` |
| mqtt | `client:m:on(event, function(conn[, topic, data])` |
| uart | `uart.on(event, cnt, [function(data)], [run_input])` |
### So how is context passed between Lua event tasks?
* It is important to understand that any event callback task is associated with a single Lua function. This function is executed from the relevant NodeMCU library C code using a `lua_call()`. Even system initialisation which executes the `dofile("init.lua")` can be treated as a special case of this. Each function can invoke other functions and so on, but it must ultimate return control to the C library code.
* By their very nature Lua `local` variables only exist within the context of an executing Lua function, and so all locals are destroyed between these `lua_call()` actions. *No locals are retained across events*.
* So context can only be passed between event routines by one of three mechanisms:
* **Globals** are by nature globally accessible. Any global will persist until explicitly dereference by reassigning `nil` to it. Globals can be readily enumerated by a `for k,v in pairs(_G) do` so their use is transparent.
* It is important to understand that any event callback task is associated with a single Lua function. This function is executed from the relevant NodeMCU library C code using a `lua_call()`. Even system initialisation which executes the `dofile("init.lua")` can be treated as a special case of this. Each function can invoke other functions and so on, but it must ultimate return control to the C library code.
* By their very nature Lua `local` variables only exist within the context of an executing Lua function, and so all locals are destroyed between these `lua_call()` actions. *No locals are retained across events*.
* So context can only be passed between event routines by one of three mechanisms:
* **Globals** are by nature globally accessible. Any global will persist until explicitly dereference by reassigning `nil` to it. Globals can be readily enumerated by a `for k,v in pairs(_G) do` so their use is transparent.
* The **File system** is a special case of persistent global, so there is no reason in principle why it can't be used to pass context. However the ESP8266 file system uses flash memory and this has a limited write cycle lifetime, so it is best to avoid using the file system to store frequently changing content except as a mechanism of last resort.
* **Upvalues**. When a function is declared within an outer function, all of the local variables in the outer scope are available to the inner function. Since all functions are stored by reference the scope of the inner function might outlast the scope of the outer function, and the Lua runtime system ensures that any such references persist for the life of any functions that reference it. This standard feature of Lua is known as *closure* and is described in [Pil 6]. Such values are often called *upvalues*. Functions which are global or [[#So how is the Lua Registry used and why is this important?|registered]] callbacks will persist between event routines, and hence any upvalues referenced by them can be used for passing context.
* **Upvalues**. When a function is declared within an outer function, all of the local variables in the outer scope are available to the inner function. Since all functions are stored by reference the scope of the inner function might outlast the scope of the outer function, and the Lua runtime system ensures that any such references persist for the life of any functions that reference it. This standard feature of Lua is known as *closure* and is described in [Pil 6]. Such values are often called *upvalues*. Functions which are global or [[#So how is the Lua Registry used and why is this important?|registered]] callbacks will persist between event routines, and hence any upvalues referenced by them can be used for passing context.
### So how is the Lua Registry used and why is this important?
......@@ -165,22 +166,22 @@ One further complication is that some library functions don't correctly derefere
### Can I encapsulate actions such as sending an email in a Lua function?
Think about the implications of these last few answers.
* An action such as composing and sending an email involves a message dialogue with a mail server over TCP. This in turn requires calling multiple API calls to the SDK and your Lua code must return control to the C calling library for this to be scheduled, otherwise these requests will just queue up, you'll run out of RAM and your application will PANIC.
* Hence it is simply ***impossible*** to write a Lua module so that you can do something like:
* An action such as composing and sending an email involves a message dialogue with a mail server over TCP. This in turn requires calling multiple API calls to the SDK and your Lua code must return control to the C calling library for this to be scheduled, otherwise these requests will just queue up, you'll run out of RAM and your application will PANIC.
* Hence it is simply ***impossible*** to write a Lua module so that you can do something like:
```lua
-- prepare message
status = mail.send(to, subject, body)
-- move on to next phase of processing.
```
* But you could code up a event-driven task to do this and pass it a callback to be executed on completion of the mail send, something along the lines of the following. Note that since this involves a lot of asynchronous processing and which therefore won't take place until you've returned control to the calling library C code, you will typically execute this as the last step in a function and therefore this is best done as a tailcall [PiL 6.3].
* But you could code up a event-driven task to do this and pass it a callback to be executed on completion of the mail send, something along the lines of the following. Note that since this involves a lot of asynchronous processing and which therefore won't take place until you've returned control to the calling library C code, you will typically execute this as the last step in a function and therefore this is best done as a tailcall [PiL 6.3].
```lua
-- prepare message
local ms = require("mail_sender")
return ms.send(to, subject, body, function(status) loadfile("process_next.lua")(status) end)
```
* Building an application on the ESP8266 is a bit like threading pearls onto a necklace. Each pearl is an event task which must be small enough to run within its RAM resources and the string is the variable context that links the pearls together.
* Building an application on the ESP8266 is a bit like threading pearls onto a necklace. Each pearl is an event task which must be small enough to run within its RAM resources and the string is the variable context that links the pearls together.
### When and why should I avoid using tmr.delay()?
......@@ -196,43 +197,43 @@ The latest SDK includes a caution that if any (callback) task runs for more than
### How do I avoid a PANIC loop in init.lua?
Most of us have fallen into the trap of creating an `init.lua` that has a bug in it, which then causes the system to reboot and hence gets stuck in a reboot loop. If you haven't then you probably will do so at least once.
* When this happens, the only robust solution is to reflash the firmware.
* The simplest way to avoid having to do this is to keep the `init.lua` as simple as possible -- say configure the wifi and then start your app using a one-time `tmr.alarm()` after a 2-3 sec delay. This delay is long enough to issue a `file.remove("init.lua")` through the serial port and recover control that way.
* Also it is always best to test any new `init.lua` by creating it as `init_test.lua`, say, and manually issuing a `dofile("init_test.lua")` through the serial port, and then only rename it when you are certain it is working as you require.
* When this happens, the only robust solution is to reflash the firmware.
* The simplest way to avoid having to do this is to keep the `init.lua` as simple as possible -- say configure the wifi and then start your app using a one-time `tmr.alarm()` after a 2-3 sec delay. This delay is long enough to issue a `file.remove("init.lua")` through the serial port and recover control that way.
* Also it is always best to test any new `init.lua` by creating it as `init_test.lua`, say, and manually issuing a `dofile("init_test.lua")` through the serial port, and then only rename it when you are certain it is working as you require.
## Techniques for Reducing RAM and SPIFFS footprint
### How do I minimise the footprint of an application?
* Perhaps the simplest aspect of reducing the footprint of an application is to get its scope correct. The ESP8266 is an IoT device and not a general purpose system. It is typically used to attach real-world monitors, controls, etc. to an intranet and is therefore designed to implement functions that have limited scope. We commonly come across developers who are trying to treat the ESP8266 as a general purpose device and can't understand why their application can't run.
* The simplest and safest way to use IoT devices is to control them through a dedicated general purpose system on the same network. This could be a low cost system such as a [RaspberryPi (RPi)](https://www.raspberrypi.org/) server, running your custom code or an open source home automation (HA) application. Such systems have orders of magnitude more capacity than the ESP8266, for example the RPi has 2GB RAM and its SD card can be up to 32GB in capacity, and it can support the full range of USB-attached disk drives and other devices. It also runs a fully featured Linux OS, and has a rich selection of applications pre configured for it. There are plenty of alternative systems available in this under $50 price range, as well as proprietary HA systems which can cost 10-50 times more.
* Using a tiered approach where all user access to the ESP8266 is passed through a controlling server means that the end-user interface (or smartphone connector), together with all of the associated validation and security can be implemented on a system designed to have the capacity to do this. This means that you can limit the scope of your ESP8266 application to a limited set of functions being sent to or responding to requests from this system.
* *If you are trying to implement a user-interface or HTTP webserver in your ESP8266 then you are really abusing its intended purpose. When it comes to scoping your ESP8266 applications, the adage **K**eep **I**t **S**imple **S**tupid truly applies.*
* Perhaps the simplest aspect of reducing the footprint of an application is to get its scope correct. The ESP8266 is an IoT device and not a general purpose system. It is typically used to attach real-world monitors, controls, etc. to an intranet and is therefore designed to implement functions that have limited scope. We commonly come across developers who are trying to treat the ESP8266 as a general purpose device and can't understand why their application can't run.
* The simplest and safest way to use IoT devices is to control them through a dedicated general purpose system on the same network. This could be a low cost system such as a [RaspberryPi (RPi)](https://www.raspberrypi.org/) server, running your custom code or an open source home automation (HA) application. Such systems have orders of magnitude more capacity than the ESP8266, for example the RPi has 2GB RAM and its SD card can be up to 32GB in capacity, and it can support the full range of USB-attached disk drives and other devices. It also runs a fully featured Linux OS, and has a rich selection of applications pre configured for it. There are plenty of alternative systems available in this under $50 price range, as well as proprietary HA systems which can cost 10-50 times more.
* Using a tiered approach where all user access to the ESP8266 is passed through a controlling server means that the end-user interface (or smartphone connector), together with all of the associated validation and security can be implemented on a system designed to have the capacity to do this. This means that you can limit the scope of your ESP8266 application to a limited set of functions being sent to or responding to requests from this system.
* *If you are trying to implement a user-interface or HTTP webserver in your ESP8266 then you are really abusing its intended purpose. When it comes to scoping your ESP8266 applications, the adage **K**eep **I**t **S**imple **S**tupid truly applies.*
### How do I minimise the footprint of an application on the file system
* It is possible to write Lua code in a very compact format which is very dense in terms of functionality per KB of source code.
* However if you do this then you will also find it extremely difficult to debug or maintain your application.
* A good compromise is to use a tool such as [LuaSrcDiet](http://luaforge.net/projects/luasrcdiet/), which you can use to compact production code for downloading to the ESP8266:
* It is possible to write Lua code in a very compact format which is very dense in terms of functionality per KB of source code.
* However if you do this then you will also find it extremely difficult to debug or maintain your application.
* A good compromise is to use a tool such as [LuaSrcDiet](http://luaforge.net/projects/luasrcdiet/), which you can use to compact production code for downloading to the ESP8266:
* Keep a master repository of your code on your PC or a cloud-based versioning repository such as [GitHub](https://github.com/)
* Lay it out and comment it for ease of maintenance and debugging
* Use a package such as [Esplorer](https://github.com/4refr0nt/ESPlorer) to download modules that you are debugging and to test them.
* Once the code is tested and stable, then compress it using LuaSrcDiet before downloading to the ESP8266. Doing this will reduce the code footprint on the SPIFFS by 2-3x.
* Consider using `node.compile()` to pre-compile any production code. This removes the debug information from the compiled code reducing its size by roughly 40%. (However this is still perhaps 1.5-2x larger than a LuaSrcDiet-compressed source format, so if SPIFFS is tight then you might consider leaving less frequently run modules in Lua format. If you do a compilation, then you should consider removing the Lua source copy from file system as there's little point in keeping both on the ESP8266.
* Consider using `node.compile()` to pre-compile any production code. This removes the debug information from the compiled code reducing its size by roughly 40%. (However this is still perhaps 1.5-2x larger than a LuaSrcDiet-compressed source format, so if SPIFFS is tight then you might consider leaving less frequently run modules in Lua format. If you do a compilation, then you should consider removing the Lua source copy from file system as there's little point in keeping both on the ESP8266.
### How do I minimise the footprint of running application?
* The Lua Garbage collector is very aggressive at scanning and recovering dead resources. It uses an incremental mark-and-sweep strategy which means that any data which is not ultimately referenced back to the Globals table, the Lua registry or in-scope local variables in the current Lua code will be collected.
* Setting any variable to `nil` dereferences the previous context of that variable. (Note that reference-based variables such as tables, strings and functions can have multiple variables referencing the same object, but once the last reference has been set to `nil`, the collector will recover the storage.
* Unlike other compile-on-load languages such as PHP, Lua compiled code is treated the same way as any other variable type when it comes to garbage collection and can be collected when fully dereferenced, so that the code-space can be reused.
* Lua execution is intrinsically divided into separate event tasks with each bound to a Lua callback. This, when coupled with the strong dispose on dereference feature, means that it is very easy to structure your application using an classic technique which dates back to the 1950s known as Overlays.
* Various approaches can be use to implement this. One is described by DP Whittaker in his [Massive memory optimization: flash functions](http://www.esp8266.com/viewtopic.php?f=19&t=1940) topic. Another is to use *volatile modules*. There are standard Lua templates for creating modules, but the `require()` library function creates a reference for the loaded module in the `package.loaded` table, and this reference prevents the module from being garbage collected. To make a module volatile, you should remove this reference to the loaded module by setting its corresponding entry in `package.loaded` to `nil`. You can't do this in the outermost level of the module (since the reference is only created once execution has returned from the module code), but you can do it in any module function, and typically an initialisation function for the module, as in the following example:
* The Lua Garbage collector is very aggressive at scanning and recovering dead resources. It uses an incremental mark-and-sweep strategy which means that any data which is not ultimately referenced back to the Globals table, the Lua registry or in-scope local variables in the current Lua code will be collected.
* Setting any variable to `nil` dereferences the previous context of that variable. (Note that reference-based variables such as tables, strings and functions can have multiple variables referencing the same object, but once the last reference has been set to `nil`, the collector will recover the storage.
* Unlike other compile-on-load languages such as PHP, Lua compiled code is treated the same way as any other variable type when it comes to garbage collection and can be collected when fully dereferenced, so that the code-space can be reused.
* Lua execution is intrinsically divided into separate event tasks with each bound to a Lua callback. This, when coupled with the strong dispose on dereference feature, means that it is very easy to structure your application using an classic technique which dates back to the 1950s known as Overlays.
* Various approaches can be use to implement this. One is described by DP Whittaker in his [Massive memory optimization: flash functions](http://www.esp8266.com/viewtopic.php?f=19&t=1940) topic. Another is to use *volatile modules*. There are standard Lua templates for creating modules, but the `require()` library function creates a reference for the loaded module in the `package.loaded` table, and this reference prevents the module from being garbage collected. To make a module volatile, you should remove this reference to the loaded module by setting its corresponding entry in `package.loaded` to `nil`. You can't do this in the outermost level of the module (since the reference is only created once execution has returned from the module code), but you can do it in any module function, and typically an initialisation function for the module, as in the following example:
```lua
local s=net.createServer(net.TCP)
s:listen(80,function(c) require("connector").init(c) end)
```
* **`connector.lua`** would be a standard module pattern except that the `M.init()` routine must include the lines
* **`connector.lua`** would be a standard module pattern except that the `M.init()` routine must include the lines
```lua
local M, module = {}, ...
......@@ -244,8 +245,8 @@ end
--
return M
```
* This approach ensures that the module can be fully dereferenced on completion. OK, in this case, this also means that the module has to be reloaded on each TCP connection to port 80; however, loading a compiled module from SPIFFS only takes a few mSec, so surely this is an acceptable overhead if it enables you to break down your application into RAM-sized chunks. Note that `require()` will automatically search for `connector.lc` followed by `connector.lua`, so the code will work for both source and compiled variants.
* Whilst the general practice is for a module to return a table, [PiL 15.1] suggests that it is sometimes appropriate to return a single function instead as this avoids the memory overhead of an additional table. This pattern would look as follows:
* This approach ensures that the module can be fully dereferenced on completion. OK, in this case, this also means that the module has to be reloaded on each TCP connection to port 80; however, loading a compiled module from SPIFFS only takes a few mSec, so surely this is an acceptable overhead if it enables you to break down your application into RAM-sized chunks. Note that `require()` will automatically search for `connector.lc` followed by `connector.lua`, so the code will work for both source and compiled variants.
* Whilst the general practice is for a module to return a table, [PiL 15.1] suggests that it is sometimes appropriate to return a single function instead as this avoids the memory overhead of an additional table. This pattern would look as follows:
```lua
--
......@@ -261,7 +262,7 @@ return function (csocket)
...
end
```
* Also note that you should ***not*** normally code this up listener call as the following because the RAM now has to accommodate both the module which creates the server *and* the connector logic.
* Also note that you should ***not*** normally code this up listener call as the following because the RAM now has to accommodate both the module which creates the server *and* the connector logic.
```lua
...
......@@ -284,8 +285,8 @@ Note that if you use `require("XXX")` to load your code then this will automatic
### How do I get a feel for how much memory my functions use?
* You should get an overall understanding of the VM model if you want to make good use of the limited resources available to Lua applications. An essential reference here is [A No Frills Introduction to Lua 5.1 VM Instructions](http://luaforge.net/docman/83/98/ANoFrillsIntroToLua51VMInstructions.pdf) . This explain how the code generator works, how much memory overhead is involved with each table, function, string etc..
* You can't easily get a bytecode listing of your ESP8266 code; however there are two broad options for doing this:
* You should get an overall understanding of the VM model if you want to make good use of the limited resources available to Lua applications. An essential reference here is [A No Frills Introduction to Lua 5.1 VM Instructions](http://luaforge.net/docman/83/98/ANoFrillsIntroToLua51VMInstructions.pdf) . This explain how the code generator works, how much memory overhead is involved with each table, function, string etc..
* You can't easily get a bytecode listing of your ESP8266 code; however there are two broad options for doing this:
* **Generate a bytecode listing on your development PC**. The Lua 5.1 code generator is basically the same on the PC and on the ESP8266, so whilst it isn't identical, using the standard Lua batch compiler `luac` against your source on your PC with the `-l -s` option will give you a good idea of what your code will generate. The main difference between these two variants is the size_t for ESP8266 is 4 bytes rather than the 8 bytes size_t found on modern 64bit development PCs; and the eLua variants generate different access references for ROM data types. If you want to see what the `string.dump()` version generates then drop the `-s` option to retain the debug information.
* **Upload your `.lc` files to the PC and disassemble then there**. There are a number of Lua code disassemblers which can list off the compiled code that you application modules will generate, `if` you have a script to upload files from your ESP8266 to your development PC. I use [ChunkSpy](http://luaforge.net/projects/chunkspy/) which can be downloaded [here](http://files.luaforge.net/releases/chunkspy/chunkspy/ChunkSpy-0.9.8/ChunkSpy-0.9.8.zip) , but you will need to apply the following patch so that ChunkSpy understands eLua data types:
......@@ -302,8 +303,8 @@ Note that if you use `require("XXX")` to load your code then this will automatic
elseif a == "--interact" then
perform = ChunkSpy_Interact
```
* Your other great friend is to use `node.heap()` regularly through your code.
* Use these tools and play with coding approaches to see how many instructions each typical line of code takes in your coding style. The Lua Wiki gives some general optimisation tips, but in general just remember that these focus on optimising for execution speed and you will be interested mainly in optimising for code and variable space as these are what consumes precious RAM.
* Your other great friend is to use `node.heap()` regularly through your code.
* Use these tools and play with coding approaches to see how many instructions each typical line of code takes in your coding style. The Lua Wiki gives some general optimisation tips, but in general just remember that these focus on optimising for execution speed and you will be interested mainly in optimising for code and variable space as these are what consumes precious RAM.
### What is the cost of using functions?
......@@ -331,11 +332,11 @@ Of course you should still use functions to structure your code and encapsulate
### What other resources are available?
* Install lua and luac on your development PC. This is freely available for Windows, Mac and Linux distributions, but we strongly suggest that you use Lua 5.1 to maintain source compatibility with ESP8266 code. This will allow you not only to unit test some modules on your PC in a rich development environment, but you can also use `luac` to generate a bytecode listing of your code and to validate new code syntactically before downloading to the ESP8266. This will also allow you to develop server-side applications and embedded applications in a common language.
* Install lua and luac on your development PC. This is freely available for Windows, Mac and Linux distributions, but we strongly suggest that you use Lua 5.1 to maintain source compatibility with ESP8266 code. This will allow you not only to unit test some modules on your PC in a rich development environment, but you can also use `luac` to generate a bytecode listing of your code and to validate new code syntactically before downloading to the ESP8266. This will also allow you to develop server-side applications and embedded applications in a common language.
## Firmware and Lua app development
### How to save memory?
* The NodeMCU development team recommends that you consider using a tailored firmware build, which only includes the modules that you plan to use before developing any Lua application. Once you have the ability to make and flash custom builds, the you also have the option of moving time sensitive or logic intensive code into your own custom module. Doing this can save a large amount of RAM as C code can be run directly from Flash memory. If you want an easy-to-use intermediate option then why note try the [cloud based NodeMCU custom build service](http://frightanic.com/NodeMCU-custom-build)?.
* The NodeMCU development team recommends that you consider using a tailored firmware build, which only includes the modules that you plan to use before developing any Lua application. Once you have the ability to make and flash custom builds, the you also have the option of moving time sensitive or logic intensive code into your own custom module. Doing this can save a large amount of RAM as C code can be run directly from Flash memory. If you want an easy-to-use intermediate option then why note try the [cloud based NodeMCU custom build service](http://frightanic.com/NodeMCU-custom-build)?.
# ADXL345 Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2016-04-08 | [Jason Schmidlapp](https://github.com/jschmidlapp) | [Jason Schmidlapp](https://github.com/jschmidlapp) | [adxl345.c](../../../app/modules/adxl345.c)|
This module provides access to the [ADXL345](https://www.sparkfun.com/products/9836) triple axis accelerometer.
## adxl345.read()
Samples the sensor and returns X,Y and Z data from the accelerometer.
#### Syntax
`adxl345.read()`
#### Returns
X,Y,Z data (integers)
#### Example
```lua
adxl345.init(1, 2)
local x,y,z = adxl345.read()
print(string.format("X = %d, Y = %d, Z = %d", x, y, z))
```
## adxl345.init()
Initializes the module and sets the pin configuration.
#### Syntax
`adxl345.init(sda, scl)`
#### Parameters
- `sda` data pin
- `scl` clock pin
#### Returns
`nil`
......@@ -6,10 +6,12 @@
The CoAP module provides a simple implementation according to [CoAP](http://tools.ietf.org/html/rfc7252) protocol.
The basic endpoint server part is based on [microcoap](https://github.com/1248/microcoap), and many other code reference [libcoap](https://github.com/obgm/libcoap).
This module implements both the client and the server side. GET/PUT/POST/DELETE is partially supported by the client. Server can register Lua functions and varibles. No observe or discover supported yet.
This module implements both the client and the server side. GET/PUT/POST/DELETE is partially supported by the client. Server can register Lua functions and variables. No observe or discover supported yet.
!!! caution
This module is only in the very early stages and not complete yet.
## Caution
This module is only in the very early stage and not complete yet.
## Constants
Constants for various functions.
......
......@@ -24,7 +24,7 @@ Read all kinds of DHT sensors, including DHT11, 21, 22, 33, 44 humidity temperat
- `temp_dec` temperature decimal
- `humi_dec` humidity decimal
!!! note "Note:"
!!! note
If using float firmware then `temp` and `humi` are floating point numbers. On an integer firmware, the final values have to be concatenated from `temp` and `temp_dec` / `humi` and `hum_dec`.
......@@ -67,7 +67,7 @@ Read DHT11 humidity temperature combo sensor.
- `temp_dec` temperature decimal
- `humi_dec` humidity decimal
!!! note "Note:"
!!! note
If using float firmware then `temp` and `humi` are floating point numbers. On an integer firmware, the final values have to be concatenated from `temp` and `temp_dec` / `humi` and `hum_dec`.
......@@ -90,7 +90,7 @@ Read all kinds of DHT sensors, except DHT11.
- `temp_dec` temperature decimal
- `humi_dec` humidity decimal
!!! note "Note:"
!!! note
If using float firmware then `temp` and `humi` are floating point numbers. On an integer firmware, the final values have to be concatenated from `temp` and `temp_dec` / `humi` and `hum_dec`.
......
......@@ -31,7 +31,7 @@ The current setting, true if manual mode is enabled, false if it is not.
#### Example
```lua
wifi.setmode(wifi.STATIONAP)
wifi.ap.config({ssid="MyPersonalSSID",auth=wifi.AUTH_OPEN})
wifi.ap.config({ssid="MyPersonalSSID", auth=wifi.OPEN})
enduser_setup.manual(true)
enduser_setup.start(
function()
......
......@@ -69,18 +69,29 @@ gpio.read(0)
## gpio.serout()
Serialize output based on a sequence of delay-times. After each delay, the pin is toggled.
Serialize output based on a sequence of delay-times in µs. After each delay, the pin is toggled. After the last repeat and last delay the pin is not toggled.
The function works in two modes:
* synchronous - for sub-50 µs resolution, restricted to max. overall duration,
* asynchrounous - synchronous operation with less granularity but virtually unrestricted duration.
Whether the asynchronous mode is chosen is defined by presence of the `callback` parameter. If present and is of function type the function goes asynchronous the callback function is invoked when sequence finishes. If the parameter is numeric the function still goes asynchronous but no callback is invoked when done.
For asynchronous version minimum delay time should not be shorter than 50 μs and maximum delay time is 0x7fffff μs (~8.3 seconds).
In this mode the function does not block the stack and returns immediately before the output sequence is finalized. HW timer inf `FRC1_SOURCE` mode is used to change the states.
Note that the synchronous variant (no or nil `callback` parameter) function blocks the stach and as such any use of it must adhere to the SDK guidelines (also explained [here](https://nodemcu.readthedocs.io/en/dev/en/extn-developer-faq/#extension-developer-faq)). Failure to do so may lead to WiFi issues or outright to crashes/reboots. Shortly it means that sum of all delay times multiplied by the number of repeats should not exceed 15 ms.
#### Syntax
`gpio.serout(pin, start_level, delay_times [, repeat_num])`
`gpio.serout(pin, start_level, delay_times [, repeat_num[, callback]])`
#### Parameters
- `pin` pin to use, IO index
- `start_level` level to start on, either `gpio.HIGH` or `gpio.LOW`
- `delay_times` an array of delay times between each toggle of the gpio pin.
- `delay_times` an array of delay times in µs between each toggle of the gpio pin.
- `repeat_num` an optional number of times to run through the sequence.
- `callback` an optional callback function or number, if present the function ruturns immediately and goes asynchronous.
Note that this function blocks, and as such any use of it must adhere to the SDK guidelines of time spent blocking the stack (10-100ms). Failure to do so may lead to WiFi issues or outright crashes/reboots.
#### Returns
`nil`
......@@ -94,6 +105,9 @@ gpio.serout(1,1,{3,7},8) -- serial 30% pwm 100k, lasts 8 cycles
gpio.serout(1,1,{0,0},8) -- serial 50% pwm as fast as possible, lasts 8 cycles
gpio.serout(1,0,{20,10,10,20,10,10,10,100}) -- sim uart one byte 0x5A at about 100kbps
gpio.serout(1,1,{8,18},8) -- serial 30% pwm 38k, lasts 8 cycles
gpio.serout(1,1,{5000,995000},100, function() print("done") end) -- asynchronous 100 flashes 5 ms long every second with a callback function when done
gpio.serout(1,1,{5000,995000},100, 1) -- asynchronous 100 flashes 5 ms long, no callback
```
## gpio.trig()
......
# HMC5883L Module
| Since | Origin / Contributor | Maintainer | Source |
| :----- | :-------------------- | :---------- | :------ |
| 2016-04-09 | [Jason Schmidlapp](https://github.com/jschmidlapp) | [Jason Schmidlapp](https://github.com/jschmidlapp) | [hmc5883l.c](../../../app/modules/hmc5883l.c)|
This module provides access to the [HMC5883L](https://www.sparkfun.com/products/10530) three axis digital compass.
## hmc5883l.read()
Samples the sensor and returns X,Y and Z data.
#### Syntax
`hmc5883l.read()`
#### Returns
x,y,z measurements (integers)
temperature multiplied with 10 (integer)
#### Example
```lua
hmc5883.init(1, 2)
local x,y,z = hmc5883l.read()
print(string.format("x = %d, y = %d, z = %d", x, y, z))
```
## hmc5883l.init()
Initializes the module and sets the pin configuration.
#### Syntax
`hmc5883l.init(sda, scl)`
#### Parameters
- `sda` data pin
- `scl` clock pin
#### Returns
`nil`
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment