CARRIER SIGNAL START

- start working on autocon via phonebook
- try to avoid autoconnecting to a guru meditation.
This commit is contained in:
Kyle Isom 2023-10-06 08:39:30 +00:00
parent bec470f6d7
commit fbeb54a830
13 changed files with 1361 additions and 8 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.pio .pio
include/homenet.h
.vscode/.browse.c_cpp.db* .vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json .vscode/c_cpp_properties.json
.vscode/launch.json .vscode/launch.json

View File

@ -26,6 +26,7 @@ deploy: $(FIRMWARE)
.PHONY: clean .PHONY: clean
clean: clean:
$(PIO) -t clean $(PIO) -t clean
rm -rf *.bin unpacked_fs
.PHONY: cloc .PHONY: cloc
cloc: cloc:
@ -35,3 +36,7 @@ cloc:
test: test:
$(PIO) -t test $(PIO) -t test
.PHONY: downloadfs
downloadfs:
$(PIO) -t downloadfs

44
include/Arena.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef KIMODEM_ARENA_H
#define KIMODEM_ARENA_H
#include <sys/stat.h>
#include <cstddef>
#include <cstdint>
typedef struct {
uint8_t *Store;
size_t Size;
int fd;
uint8_t Type;
} Arena;
/*
* InitializeArena is intended for use only with systems that
* do not initialize new variables to zero. It should be called
* exactly once, at the start of the program. Any other time the
* arena needs to be reset, it should be called with clear_arena
* or destroy_arena.
*/
void InitializeArena(Arena &arena);
int NewStaticArena(Arena &, uint8_t *, size_t);
int AllocNewArena(Arena &, size_t);
#if defined(__linux__)
int MMapArena(Arena &, int); /* arena will own fd */
int CreateArena(Arena &arena, const char *path, size_t size, mode_t mode);
int OpenArena(Arena &, const char *, size_t);
#endif
void ClearArena(Arena &);
int DestroyArena(Arena &); /* dispose of any memory used by arena */
/* DANGER: if arena is file backed (mmap or open), DO NOT WRITE TO THE
* BACKING FILE! */
int WriteArena(const Arena &arena, const char *path);
void DisplayArena(const Arena &arena);
#endif

44
include/Dictionary.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef KLIB_DICTIONARY_H
#define KLIB_DICTIONARY_H
#include "Arena.h"
#include "TLV.h"
#define DICTIONARY_TAG_KEY 1
#define DICTIONARY_TAG_VAL 2
/*
* A Dictionary is a collection of key-value pairs, similar to how
* a dictionary is a mapping of names to definitions.
*/
class Dictionary {
public:
Dictionary(Arena &arena) :
arena(arena),
kTag(DICTIONARY_TAG_KEY),
vTag(DICTIONARY_TAG_VAL) {} ;
Dictionary(Arena &arena, uint8_t kt, uint8_t vt) :
arena(arena),
kTag(kt),
vTag(vt) {};
bool Lookup(const char *key, uint8_t klen, TLV::Record &res);
int Set(const char *key, uint8_t klen, const char *val,
uint8_t vlen);
bool Has(const char *key, uint8_t klen);
void DumpKVPairs();
void DumpToFile(const char *path);
private:
uint8_t *seek(const char *key, uint8_t klen);
bool spaceAvailable(uint8_t klen, uint8_t vlen);
Arena &arena;
uint8_t kTag;
uint8_t vTag;
};
#endif

48
include/TLV.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef KIMODEM_TLV_H
#define KIMODEM_TLV_H
#include <cstdint>
#include "Arena.h"
#ifndef TLV_MAX_LEN
#define TLV_MAX_LEN 253
#endif
#define TAG_EMPTY 0
namespace TLV {
struct Record {
uint8_t Tag;
uint8_t Len;
char Val[TLV_MAX_LEN];
};
uint8_t *WriteToMemory(Arena &, uint8_t *, Record &);
void ReadFromMemory(Record &, uint8_t *);
void SetRecord(Record &, uint8_t, uint8_t, const char *);
void DeleteRecord(Arena &, uint8_t *);
/*
* returns a pointer to memory where the record was found,
* e.g. LocateTag(...)[0] is the tag of the found record.
* FindTag will call LocateTag and then SkipRecord if the
* tag was found.
*/
uint8_t *FindTag(Arena &, uint8_t *, Record &);
uint8_t *LocateTag(Arena &, uint8_t *, Record &);
uint8_t *FindEmpty(Arena &, uint8_t *);
uint8_t *SkipRecord(Record &, uint8_t *);
} // namespace TLV
#endif

15
include/WiFiMgr.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef KIMODEM_WIFI_H
#define KIMODEM_WIFI_H
#include "Dictionary.h"
#include "WiFiMgr.h"
bool SetupWiFi();
bool Autoconnect(Dictionary &pb);
bool Autoconnect(Dictionary &pb, bool reset);
#endif

View File

@ -8,7 +8,11 @@
; Please visit documentation for the other options and examples ; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[platformio]
[env:sparkfun_esp32micromod] [env:sparkfun_esp32micromod]
platform = espressif32 platform = espressif32
board = sparkfun_esp32micromod board = sparkfun_esp32micromod
framework = arduino framework = arduino
monitor_speed = 115200
extra_scripts = scripts/download_fs.py

341
scripts/download_fs.py Normal file
View File

@ -0,0 +1,341 @@
# Written by Maximilian Gerhardt <maximilian.gerhardt@rub.de>
# 29th December 2020
# License: Apache
# Expanded from functionality provided by PlatformIO's espressif32 and espressif8266 platforms, credited below.
# This script provides functions to download the filesystem (SPIFFS or LittleFS) from a running ESP32 / ESP8266
# over the serial bootloader using esptool.py, and mklittlefs / mkspiffs for extracting.
# run by either using the VSCode task "Custom" -> "Download Filesystem"
# or by doing 'pio run -t downloadfs' (with optional '-e <environment>') from the commandline.
# output will be saved, by default, in the "unpacked_fs" of the project.
# this folder can be changed by writing 'custom_unpack_dir = some_other_dir' in the corresponding platformio.ini
# environment.
import re
import sys
from os.path import isfile, join
from enum import Enum
import typing
from platformio.builder.tools.pioupload import AutodetectUploadPort
import os
import subprocess
import shutil
import shlex
Import("env")
platform = env.PioPlatform()
board = env.BoardConfig()
mcu = board.get("build.mcu", "esp32")
# needed for later
AutodetectUploadPort(env)
class FSType(Enum):
SPIFFS="spiffs"
LITTLEFS="littlefs"
FATFS="fatfs"
class FSInfo:
def __init__(self, fs_type, start, length, page_size, block_size):
self.fs_type = fs_type
self.start = start
self.length = length
self.page_size = page_size
self.block_size = block_size
def __repr__(self):
return f"FS type {self.fs_type} Start {hex(self.start)} Len {self.length} Page size {self.page_size} Block size {self.block_size}"
# extract command supposed to be implemented by subclasses
def get_extract_cmd(self):
raise NotImplementedError()
class LittleFSInfo(FSInfo):
def __init__(self, start, length, page_size, block_size):
if env["PIOPLATFORM"] == "espressif32":
#for ESP32: retrieve and evaluate, e.g. to mkspiffs_espressif32_arduino
#Espressif32 Framework 3.X.X: MKSPIFFSTOOL
#Espressif32 Framework 4.X.X and 5.X.X: MKFSTOOL
if "MKSPIFFSTOOL" in env:
self.tool = env.subst(env["MKSPIFFSTOOL"])
else:
self.tool = env.subst(env["MKFSTOOL"])
else:
self.tool = env["MKFSTOOL"] # from mkspiffs package
self.tool = join(platform.get_package_dir("tool-mklittlefs"), self.tool)
super().__init__(FSType.LITTLEFS, start, length, page_size, block_size)
def __repr__(self):
return f"FS type {self.fs_type} Start {hex(self.start)} Len {self.length} Page size {self.page_size} Block size {self.block_size} Tool: {self.tool}"
def get_extract_cmd(self, input_file, output_dir):
return [self.tool, "-b", str(self.block_size), "-p", str(self.page_size), "--unpack", output_dir, input_file]
class SPIFFSInfo(FSInfo):
def __init__(self, start, length, page_size, block_size):
if env["PIOPLATFORM"] == "espressif32":
#for ESP32: retrieve and evaluate, e.g. to mkspiffs_espressif32_arduino
#Espressif32 Framework 3.X.X: MKSPIFFSTOOL
#Espressif32 Framework 4.X.X and 5.X.X: MKFSTOOL
if "MKSPIFFSTOOL" in env:
self.tool = env.subst(env["MKSPIFFSTOOL"])
else:
self.tool = env.subst(env["MKFSTOOL"])
else:
self.tool = env["MKFSTOOL"] # from mkspiffs package
self.tool = join(platform.get_package_dir("tool-mkspiffs"), self.tool)
super().__init__(FSType.SPIFFS, start, length, page_size, block_size)
def __repr__(self):
return f"FS type {self.fs_type} Start {hex(self.start)} Len {self.length} Page size {self.page_size} Block size {self.block_size} Tool: {self.tool}"
def get_extract_cmd(self, input_file, output_dir):
return [self.tool, "-b", str(self.block_size), "-p", str(self.page_size), "--unpack", output_dir, input_file]
# SPIFFS helpers copied from ESP32, https://github.com/platformio/platform-espressif32/blob/develop/builder/main.py
# Copyright 2014-present PlatformIO <contact@platformio.org>
# Licensed under the Apache License, Version 2.0 (the "License");
def _parse_size(value):
if isinstance(value, int):
return value
elif value.isdigit():
return int(value)
elif value.startswith("0x"):
return int(value, 16)
elif value[-1].upper() in ("K", "M"):
base = 1024 if value[-1].upper() == "K" else 1024 * 1024
return int(value[:-1]) * base
return value
def _parse_partitions(env):
partitions_csv = env.subst("$PARTITIONS_TABLE_CSV")
if not isfile(partitions_csv):
sys.stderr.write("Could not find the file %s with partitions "
"table.\n" % partitions_csv)
env.Exit(1)
return
result = []
next_offset = 0
with open(partitions_csv) as fp:
for line in fp.readlines():
line = line.strip()
if not line or line.startswith("#"):
continue
tokens = [t.strip() for t in line.split(",")]
if len(tokens) < 5:
continue
partition = {
"name": tokens[0],
"type": tokens[1],
"subtype": tokens[2],
"offset": tokens[3] or next_offset,
"size": tokens[4],
"flags": tokens[5] if len(tokens) > 5 else None
}
result.append(partition)
next_offset = (_parse_size(partition['offset']) +
_parse_size(partition['size']))
return result
def esp32_fetch_spiffs_size(env):
spiffs = None
for p in _parse_partitions(env):
if p['type'] == "data" and p['subtype'] == "spiffs":
spiffs = p
if not spiffs:
sys.stderr.write(
env.subst("Could not find the `spiffs` section in the partitions "
"table $PARTITIONS_TABLE_CSV\n"))
env.Exit(1)
return
env["SPIFFS_START"] = _parse_size(spiffs['offset'])
env["SPIFFS_SIZE"] = _parse_size(spiffs['size'])
env["SPIFFS_PAGE"] = int("0x100", 16)
env["SPIFFS_BLOCK"] = int("0x1000", 16)
## FS helpers for ESP8266
# copied from https://github.com/platformio/platform-espressif8266/blob/develop/builder/main.py
# Copyright 2014-present PlatformIO <contact@platformio.org>
# Licensed under the Apache License, Version 2.0 (the "License");
def _get_board_f_flash(env):
frequency = env.subst("$BOARD_F_FLASH")
frequency = str(frequency).replace("L", "")
return int(int(frequency) / 1000000)
def _parse_ld_sizes(ldscript_path):
assert ldscript_path
result = {}
# get flash size from board's manifest
result['flash_size'] = int(env.BoardConfig().get("upload.maximum_size", 0))
# get flash size from LD script path
match = re.search(r"\.flash\.(\d+[mk]).*\.ld", ldscript_path)
if match:
result['flash_size'] = _parse_size(match.group(1))
appsize_re = re.compile(
r"irom0_0_seg\s*:.+len\s*=\s*(0x[\da-f]+)", flags=re.I)
filesystem_re = re.compile(
r"PROVIDE\s*\(\s*_%s_(\w+)\s*=\s*(0x[\da-f]+)\s*\)" % "FS"
if "arduino" in env.subst("$PIOFRAMEWORK")
else "SPIFFS",
flags=re.I,
)
with open(ldscript_path) as fp:
for line in fp.readlines():
line = line.strip()
if not line or line.startswith("/*"):
continue
match = appsize_re.search(line)
if match:
result['app_size'] = _parse_size(match.group(1))
continue
match = filesystem_re.search(line)
if match:
result['fs_%s' % match.group(1)] = _parse_size(
match.group(2))
return result
def _get_flash_size(env):
ldsizes = _parse_ld_sizes(env.GetActualLDScript())
if ldsizes['flash_size'] < 1048576:
return "%dK" % (ldsizes['flash_size'] / 1024)
return "%dM" % (ldsizes['flash_size'] / 1048576)
def esp8266_fetch_fs_size(env):
ldsizes = _parse_ld_sizes(env.GetActualLDScript())
for key in ldsizes:
if key.startswith("fs_"):
env[key.upper()] = ldsizes[key]
assert all([
k in env
for k in ["FS_START", "FS_END", "FS_PAGE", "FS_BLOCK"]
])
# esptool flash starts from 0
for k in ("FS_START", "FS_END"):
_value = 0
if env[k] < 0x40300000:
_value = env[k] & 0xFFFFF
elif env[k] < 0x411FB000:
_value = env[k] & 0xFFFFFF
_value -= 0x200000 # correction
else:
_value = env[k] & 0xFFFFFF
_value += 0xE00000 # correction
env[k] = _value
def esp8266_get_esptoolpy_reset_flags(resetmethod):
# no dtr, no_sync
resets = ("no_reset_no_sync", "soft_reset")
if resetmethod == "nodemcu":
# dtr
resets = ("default_reset", "hard_reset")
elif resetmethod == "ck":
# no dtr
resets = ("no_reset", "soft_reset")
return ["--before", resets[0], "--after", resets[1]]
## Script interface functions
def get_fs_type_start_and_length():
platform = env["PIOPLATFORM"]
if platform == "espressif32":
print("Retrieving filesystem info for ESP32. Assuming SPIFFS.")
print("Partition file: " + str(env.subst("$PARTITIONS_TABLE_CSV")))
esp32_fetch_spiffs_size(env)
return SPIFFSInfo(env["SPIFFS_START"], env["SPIFFS_SIZE"], env["SPIFFS_PAGE"], env["SPIFFS_BLOCK"])
elif platform == "espressif8266":
print("Retrieving filesystem info for ESP8266.")
filesystem = board.get("build.filesystem", "spiffs")
if filesystem not in ("spiffs", "littlefs"):
print("Unrecognized board_build.filesystem option '" + str(filesystem) + "'.")
env.Exit(1)
# fetching sizes is the same for all filesystems
esp8266_fetch_fs_size(env)
print("FS_START: " + hex(env["FS_START"]))
print("FS_END: " + hex(env["FS_END"]))
print("FS_PAGE: " + hex(env["FS_PAGE"]))
print("FS_BLOCK: " + hex(env["FS_BLOCK"]))
if filesystem == "spiffs":
print("Recognized SPIFFS filesystem.")
return SPIFFSInfo(env["FS_START"], env["FS_END"] - env["FS_START"], env["FS_PAGE"], env["FS_BLOCK"])
elif filesystem == "littlefs":
print("Recognized LittleFS filesystem.")
return LittleFSInfo(env["FS_START"], env["FS_END"] - env["FS_START"], env["FS_PAGE"], env["FS_BLOCK"])
else:
print("Unrecongized configuration.")
pass
def download_fs(fs_info: FSInfo):
esptoolpy = join(platform.get_package_dir("tool-esptoolpy") or "", "esptool.py")
fs_file = join(env["PROJECT_DIR"], f"downloaded_fs_{hex(fs_info.start)}_{hex(fs_info.length)}.bin")
esptoolpy_cmd = [
env["PYTHONEXE"],
esptoolpy,
"--chip", mcu,
"--port", env.subst("$UPLOAD_PORT"),
"--baud", env.subst("$UPLOAD_SPEED"),
"--before", "default_reset",
"--after", "hard_reset",
"read_flash",
hex(fs_info.start),
hex(fs_info.length),
fs_file
]
print("Executing flash download command.")
print(shlex.join(esptoolpy_cmd))
try:
subprocess.call(esptoolpy_cmd)
print("Downloaded filesystem binary.")
return (True, fs_file)
except subprocess.CalledProcessError as exc:
print("Downloading failed with " + str(exc))
return (False, "")
def unpack_fs(fs_info: FSInfo, downloaded_file: str):
# by writing custom_unpack_dir = some_dir in the platformio.ini, one can
# control the unpack directory
unpack_dir = env.GetProjectOption("custom_unpack_dir", "unpacked_fs")
#unpack_dir = "unpacked_fs"
try:
if os.path.exists(unpack_dir):
shutil.rmtree(unpack_dir)
except Exception as exc:
print("Exception while attempting to remove the folder '" + str(unpack_dir) + "': " + str(exc))
if not os.path.exists(unpack_dir):
os.makedirs(unpack_dir)
cmd = fs_info.get_extract_cmd(downloaded_file, unpack_dir)
print("Executing extraction command:", shlex.join(cmd))
try:
subprocess.call(cmd)
print("Unpacked filesystem.")
return (True, unpack_dir)
except subprocess.CalledProcessError as exc:
print("Unpacking filesystem failed with " + str(exc))
return (False, "")
def display_fs(extracted_dir):
# extract command already nicely lists all extracted files.
# no need to display that ourselves. just display a summary
file_count = sum([len(files) for r, d, files in os.walk(extracted_dir)])
print("Extracted " + str(file_count) + " file(s) from filesystem.")
def command_download_fs(*args, **kwargs):
print("Entrypoint")
#print(env.Dump())
info = get_fs_type_start_and_length()
print("Parsed FS info: " + str(info))
download_ok, downloaded_file = download_fs(info)
print("Download was okay: " + str(download_ok) + ". File at: "+ str(downloaded_file))
unpack_ok, unpacked_dir = unpack_fs(info, downloaded_file)
if unpack_ok is True:
display_fs(unpacked_dir)
env.AddCustomTarget(
name="downloadfs",
dependencies=None,
actions=[
command_download_fs
],
title="Download Filesystem",
description="Downloads and displays files stored in the target ESP32/ESP8266"
)

291
src/Arena.cc Normal file
View File

@ -0,0 +1,291 @@
#if defined(__linux__) || defined(MSVC)
#include <stdio.h>
#endif
#include <stdlib.h>
#include <string.h>
#if defined(__linux__)
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#endif
#if defined(DESKTOP_BUILD)
#include <iostream>
#include <ios>
#endif
#if defined(ESP_PLATFORM)
#include <Arduino.h>
#include <SPIFFS.h>
#endif
#include "Arena.h"
#define ARENA_UNINIT 0
#define ARENA_STATIC 1
#define ARENA_ALLOC 2
#if defined(__linux__)
#define ARENA_MMAP 3
#define PROT_RW PROT_READ|PROT_WRITE
#endif
void
InitializeArena(Arena &arena)
{
arena.Store = NULL;
arena.Size = 0;
arena.Type = ARENA_UNINIT;
arena.fd = 0;
}
int
NewStaticArena(Arena &arena, uint8_t *mem, size_t size)
{
arena.Store = mem;
arena.Size = size;
arena.Type = ARENA_STATIC;
return 0;
}
int
AllocNewArena(Arena & arena, size_t size)
{
if (arena.Size > 0) {
if (DestroyArena(arena) != 0) {
return -1;
}
}
arena.Type = ARENA_ALLOC;
arena.Size = size;
arena.Store = (uint8_t *)calloc(sizeof(uint8_t), size);
if (arena.Store == NULL) {
return -1;
}
return 0;
}
#if defined(__linux__)
int
MMapArena(Arena &arena, int fd, size_t size)
{
if (arena.Size > 0) {
if (DestroyArena(arena) != 0) {
return -1;
}
}
arena.Type = ARENA_MMAP;
arena.Size = size;
arena.Store = (uint8_t *)mmap(NULL, size, PROT_RW, MAP_SHARED, fd, 0);
if ((void *)arena.Store == MAP_FAILED) {
return -1;
}
arena.fd = fd;
return 0;
}
int
OpenArena(Arena &arena, const char *path)
{
struct stat st;
if (arena.Size > 0) {
if (DestroyArena(arena) != 0) {
return -1;
}
}
if (stat(path, &st) != 0) {
return -1;
}
arena.fd = open(path, O_RDWR);
if (arena.fd == -1) {
return -1;
}
return MMapArena(arena, arena.fd, (size_t)st.st_size);
}
int
CreateArena(Arena &arena, const char *path, size_t size, mode_t mode)
{
int fd = 0;
if (arena.Size > 0) {
if (DestroyArena(arena) != 0) {
return -1;
}
}
fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
if (fd == -1) {
return -1;
}
if (ftruncate(fd, size) == -1) {
return -1;
}
close(fd);
return OpenArena(arena, path);
}
#endif
/*
* ClearArena clears the memory being used, removing any data
* present. It does not free the memory; it is effectively a
* wrapper around memset.
*/
void
ClearArena(Arena &arena)
{
if (arena.Size == 0) {
return;
}
memset(arena.Store, 0, arena.Size);
}
int
DestroyArena(Arena &arena)
{
if (arena.Type == ARENA_UNINIT) {
return 0;
}
switch (arena.Type) {
case ARENA_STATIC:
break;
case ARENA_ALLOC:
free(arena.Store);
break;
#if defined(__linux__)
case ARENA_MMAP:
if (munmap(arena.Store, arena.Size) == -1) {
return -1;
}
if (close(arena.fd) == -1) {
return -1;
}
arena.fd = 0;
break;
#endif
default:
#if defined(NDEBUG)
return -1;
#else
abort();
#endif
}
arena.Type = ARENA_UNINIT;
arena.Size = 0;
arena.Store = NULL;
return 0;
}
#if defined(DESKTOP_BUILD)
void
DisplayArena(const Arena &arena)
{
std::cout << "Arena @ 0x";
std::cout << std::hex << (uintptr_t)&arena << std::endl;
std::cout << std::dec;
std::cout << "\tStore is " << arena.Size << " bytes at address 0x";
std::cout << std::hex << (uintptr_t)&(arena.Store) << std::endl;
std::cout << "\tType: ";
switch (arena.Type) {
case ARENA_UNINIT:
std::cout << "uninitialized";
break;
case ARENA_STATIC:
std::cout << "static";
break;
case ARENA_ALLOC:
std::cout << "allocated";
break;
#if defined(__linux__)
case ARENA_MMAP:
std::cout << "mmap/file";
break;
#endif
default:
std::cout << "unknown (this is a bug)";
}
std::cout << std::endl;
}
#else
void
DisplayArena(const Arena &arena)
{
}
#endif
#if defined(__linux__) || defined(__MSVC__)
int
WriteArena(const Arena &arena, const char *path)
{
FILE *arenaFile = NULL;
int retc = -1;
arenaFile = fopen(path, "w");
if (arenaFile == NULL) {
return -1;
}
if (fwrite(arena.Store, sizeof(*arena.Store), arena.Size,
arenaFile) == arena.Size) {
retc = 0;
}
if (fclose(arenaFile) != 0) {
return -1;
}
return retc;
}
#elif defined(ESP_PLATFORM)
int
WriteArena(const Arena &arena, const char *path)
{
File arenaDataFile = SPIFFS.open(path, FILE_WRITE);
size_t offset = 0;
size_t written = 0;
while (written < arena.Size) {
offset = arenaDataFile.write(arena.Store + written, arena.Size - written);
if (offset == 0) {
break;
}
written += offset;
}
if (written != arena.Size) {
return -1;
}
return 0;
}
#endif

186
src/Dictionary.cc Normal file
View File

@ -0,0 +1,186 @@
#include <Arduino.h>
#include <cstring>
#include <cstdlib>
#include "Dictionary.h"
#if defined(DESKTOP_BUILD)
#include <iostream>
#endif
bool
Dictionary::Lookup(const char *key, uint8_t klen, TLV::Record &res)
{
res.Tag = this->kTag;
uint8_t *cursor = TLV::FindTag(this->arena, NULL, res);
while ((cursor != NULL) && (res.Tag != TAG_EMPTY)) {
if (klen != res.Len) {
cursor = TLV::FindTag(this->arena, cursor, res);
continue;
}
if (memcmp(res.Val, key, klen) == 0) {
TLV::ReadFromMemory(res, cursor);
if (res.Tag != this->vTag) {
abort();
}
return true;
}
cursor = TLV::FindTag(this->arena, cursor, res);
}
return false;
}
int
Dictionary::Set(const char *key, uint8_t klen, const char *val, uint8_t vlen)
{
TLV::Record rec;
uint8_t *cursor = NULL;
SetRecord(rec, this->kTag, klen, key);
cursor = this->seek(key, klen);
if (cursor != NULL) {
TLV::DeleteRecord(this->arena, cursor);
TLV::DeleteRecord(this->arena, cursor);
}
if (!spaceAvailable(klen, vlen)) {
return -1;
}
cursor = TLV::WriteToMemory(this->arena, NULL, rec);
if (cursor == NULL) {
return -1;
}
SetRecord(rec, this->vTag, vlen, val);
if (TLV::WriteToMemory(this->arena, NULL, rec) == NULL) {
return -1;
}
return 0;
}
uint8_t *
Dictionary::seek(const char *key, uint8_t klen)
{
TLV::Record rec;
rec.Tag = this->kTag;
uint8_t *cursor = TLV::LocateTag(this->arena, NULL, rec);
while (cursor != NULL) {
if ((klen == rec.Len) && (this->kTag == rec.Tag)) {
if (memcmp(rec.Val, key, klen) == 0) {
return cursor;
}
}
cursor = TLV::SkipRecord(rec, cursor);
cursor = TLV::LocateTag(this->arena, cursor, rec);
}
return NULL;
}
bool
Dictionary::Has(const char *key, uint8_t klen)
{
return this->seek(key, klen) != NULL;
}
bool
Dictionary::spaceAvailable(uint8_t klen, uint8_t vlen)
{
size_t required = 0;
uintptr_t remaining = 0;
uint8_t *cursor = NULL;
cursor = TLV::FindEmpty(this->arena, NULL);
if (cursor == NULL) {
return false;
}
required += klen + 2;
required += vlen + 2;
remaining = (uintptr_t)cursor - (uintptr_t)arena.Store;
remaining = arena.Size - remaining;
return ((size_t)remaining >= required);
}
#if defined(DESKTOP_BUILD)
void
Dictionary::DumpKVPairs()
{
uint8_t *cursor = (this->arena).Store;
TLV::Record rec;
TLV::ReadFromMemory(rec, cursor);
std::cout << "Dictionary KV pairs" << std::endl;
if (rec.Tag == TAG_EMPTY) {
std::cout << "\t(NONE)" << std::endl;
return;
}
while ((cursor != NULL) && (rec.Tag != TAG_EMPTY)) {
std::cout << "\t" << rec.Val << "->";
cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor);
std::cout << rec.Val << std::endl;
cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor);
}
}
#elif defined(ESP_PLATFORM)
#include <Arduino.h>
void
Dictionary::DumpKVPairs()
{
uint8_t *cursor = (this->arena).Store;
TLV::Record rec;
TLV::ReadFromMemory(rec, cursor);
Serial.println("dictionary entries: {");
if (rec.Tag == TAG_EMPTY) {
Serial.println("\tNONE");
Serial.println("}");
return;
}
while ((cursor != NULL) && (rec.Tag != TAG_EMPTY)) {
Serial.print("\t");
Serial.print(rec.Val);
Serial.print("->");
cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor);
Serial.println(rec.Val);
cursor = TLV::SkipRecord(rec, cursor);
TLV::ReadFromMemory(rec, cursor);
}
Serial.println("}");
}
#else
void
Dictionary::DumpKVPairs(const char *label)
{
}
#endif
void
Dictionary::DumpToFile(const char *path)
{
WriteArena(this->arena, path);
}

219
src/TLV.cc Normal file
View File

@ -0,0 +1,219 @@
#include <cstring>
#include "TLV.h"
#if defined(ESP_PLATFORM)
#include <Arduino.h>
#endif
#define REC_SIZE(x) ((std::size_t)x.Len + 2)
namespace TLV {
static inline size_t
spaceRemaining(const Arena &arena, const uint8_t *cursor)
{
uintptr_t remaining = 0;
if (cursor == NULL) {
cursor = arena.Store;
}
remaining = (uintptr_t)cursor - (uintptr_t)arena.Store;
remaining = arena.Size - remaining;
return remaining;
}
static bool
cursorInArena(const Arena &arena, const uint8_t *cursor)
{
if (cursor == NULL) {
return false;
}
uint8_t *end = arena.Store + spaceRemaining(arena, NULL);
if (cursor < arena.Store) {
return false;
}
return cursor < end;
}
static bool
spaceAvailable(const Arena &arena, const uint8_t *cursor, const uint8_t len)
{
uintptr_t remaining = 0;
if (cursor == NULL) {
return false;
}
remaining = (uintptr_t)cursor - (uintptr_t)arena.Store;
remaining = arena.Size - remaining;
return ((size_t)remaining > ((size_t)len+2));
}
static inline void
clearUnused(Record &rec)
{
uint8_t trail = TLV_MAX_LEN-rec.Len;
memset(rec.Val+rec.Len, 0, trail);
}
uint8_t *
WriteToMemory(Arena &arena, uint8_t *cursor, Record &rec)
{
// If cursor is NULL, the user needs us to select an empty
// slot for the record. If we can't find one, that's an
// error.
//
// If, however, the user gives us a cursor, we'll trust it
// (though spaceAvailable will sanity check that cursor).
if (cursor == NULL) {
cursor = FindEmpty(arena, cursor);
if (cursor == NULL) {
return NULL;
}
}
if (!spaceAvailable(arena, cursor, rec.Len)) {
return NULL;
}
memcpy(cursor, &rec, REC_SIZE(rec));
cursor = SkipRecord(rec, cursor);
return cursor;
}
void
SetRecord(Record &rec, uint8_t tag, uint8_t len, const char *val)
{
rec.Tag = tag;
rec.Len = len;
memcpy(rec.Val, val, len);
clearUnused(rec);
}
void
ReadFromMemory(Record &rec, uint8_t *cursor)
{
rec.Tag = cursor[0];
rec.Len = cursor[1];
memcpy(rec.Val, cursor+2, rec.Len);
clearUnused(rec);
}
/*
* returns a pointer to memory where the record was found,
* e.g. FindTag(...)[0] is the tag of the found record.
*/
uint8_t *
FindTag(Arena &arena, uint8_t *cursor, Record &rec)
{
cursor = LocateTag(arena, cursor, rec);
if (cursor == NULL) {
return NULL;
}
if (rec.Tag != TAG_EMPTY) {
cursor = SkipRecord(rec, cursor);
}
return cursor;
}
uint8_t *
LocateTag(Arena &arena, uint8_t *cursor, Record &rec)
{
uint8_t tag, len;
if (cursor == NULL) {
cursor = arena.Store;
}
if (!cursorInArena(arena, cursor)) {
return NULL;
}
while ((tag = cursor[0]) != rec.Tag) {
// We could call SkipRecord, but we already need
// to pull the length to figure out if we're at
// the end or not.
len = cursor[1];
if ((tag == TAG_EMPTY) && (len == 0)) {
return NULL;
}
if (!spaceAvailable(arena, cursor, len)) {
return NULL;
}
cursor += len;
cursor += 2;
}
if (tag != rec.Tag) {
return NULL;
}
if (tag != TAG_EMPTY) {
ReadFromMemory(rec, cursor);
}
return cursor;
}
uint8_t *
FindEmpty(Arena &arena, uint8_t *cursor) {
Record rec;
rec.Tag = TAG_EMPTY;
return FindTag(arena, cursor, rec);
}
uint8_t *
SkipRecord(Record &rec, uint8_t *cursor)
{
return (uint8_t *)((uintptr_t)cursor + rec.Len + 2);
}
void
DeleteRecord(Arena &arena, uint8_t *cursor)
{
if (cursor == NULL) {
return;
}
uint8_t len = cursor[1] + 2;
uint8_t *stop = arena.Store + arena.Size;
stop -= len;
while (cursor != stop) {
cursor[0] = cursor[len];
cursor++;
}
stop += len;
while (cursor != stop) {
cursor[0] = 0;
cursor++;
}
}
} // namespace TLV

95
src/WiFiMgr.cc Normal file
View File

@ -0,0 +1,95 @@
#include <Arduino.h>
#include <WiFi.h>
#include <string.h>
#include "Dictionary.h"
#include "TLV.h"
#include "WiFiMgr.h"
#define MAX_WAIT 10000
#define CONN_WAIT 250
bool
SetupWiFi()
{
WiFi.mode(WIFI_STA);
return true;
}
static bool
tryConnect(Dictionary &pb, int network)
{
const char *ssid = WiFi.SSID(network).c_str();
size_t ssidLen = strnlen(ssid, TLV_MAX_LEN);
TLV::Record password;
size_t waitedFor = 0;
if (ssidLen == 0) {
return false;
}
Serial.print("MODEM: CHECK ");
Serial.println(ssid);
if (!pb.Lookup(ssid, uint8_t(ssidLen), password)) {
Serial.println("SSID NOT IN PHONEBOOK");
return false;
}
Serial.println("CONNECTING");
WiFi.begin(ssid, password.Val);
while ((WiFi.status() != WL_CONNECTED) && (waitedFor < MAX_WAIT)) {
waitedFor += CONN_WAIT;
if ((waitedFor % 1000) == 0) {
Serial.print(".");
}
delay(250);
}
if (WiFi.status() == WL_CONNECTED) {
Serial.print("MODEM ADDR ");
Serial.println(WiFi.localIP());
return true;
}
Serial.println("CARRIER SIGNAL LOST");
return false;
}
bool
Autoconnect(Dictionary &pb)
{
Autoconnect(pb, true);
}
bool
Autoconnect(Dictionary &pb, bool reset)
{
int networkCount = 0;
int network = 0;
Serial.println("MODEM: TRY AUTOCONNECT");
if (reset) {
Serial.println("RADIO RESET");
SetupWiFi();
WiFi.disconnect();
delay(1000);
} else {
Serial.println("NO RESET");
}
networkCount = WiFi.scanNetworks();
for (network = 0; network < networkCount; network++) {
if (tryConnect(pb, network)) {
return true;
}
}
Serial.println("NO CARRIER");
return false;
}

View File

@ -1,18 +1,78 @@
#include <Arduino.h> #include <Arduino.h>
#include <SPIFFS.h>
#include "Arena.h"
#include "Dictionary.h"
#include "WiFiMgr.h"
#include "homenet.h"
constexpr size_t PHONEBOOK_SIZE = 512;
constexpr char pbFilePath[] = "/pb.dat";
uint8_t phonebookBuffer[PHONEBOOK_SIZE];
Arena arena;
Dictionary phonebook(arena);
static bool
setupPhonebook()
{
File pbFile = SPIFFS.open(pbFilePath, FILE_READ);
size_t fileSize = pbFile.size();
bool ok = false;
Serial.print("DAT FILE ");
Serial.print(fileSize);
Serial.println("B");
if (fileSize == 0) {
Serial.println("INIT PHONEBOOK");
phonebook.Set(HOME_SSID, HOME_SSIDLEN, HOME_WPA, HOME_WPALEN);
if (WriteArena(arena, pbFilePath) == 0) {
ok = true;
}
pbFile.close();
return ok;
}
fileSize = fileSize > PHONEBOOK_SIZE ? PHONEBOOK_SIZE : fileSize;
Serial.print("LOAD PHONEBOOK ");
if (fileSize != pbFile.read(phonebookBuffer, fileSize)) {
Serial.println("FAILED");
pbFile.close();
return false;
}
Serial.println("OK");
pbFile.close();
phonebook.DumpKVPairs();
return true;
}
// put function declarations here:
int myFunction(int, int);
void setup() { void setup() {
// put your setup code here, to run once: Serial.begin(115200);
int result = myFunction(2, 3); while (!Serial) ;
Serial.println("MODEM BOOT");
InitializeArena(arena);
NewStaticArena(arena, phonebookBuffer, PHONEBOOK_SIZE);
if (!SPIFFS.begin(true) && !SPIFFS.begin(false)) {
Serial.println("SPIFFS BEGIN FAIL");
while (true) ;
}
setupPhonebook();
while (!Autoconnect(phonebook)) {
Serial.println("STANDBY");
delay(1000);
}
Serial.println("MODEM READY");
} }
void loop() { void loop() {
// put your main code here, to run repeatedly: // put your main code here, to run repeatedly:
} }
// put function definitions here:
int myFunction(int x, int y) {
return x + y;
}