* updated dal

* refactor controller api

* use serial/spi base class

* more cleanup

* esp32spi -> esp32

* fix panic

* fix panic constants

* move response to net

* moving lowercase to pxt

* more request to net

* build fixes

* refactor net class

* using net instance

* bump pxt

* updated azureiot

* add secrets api in seattings

* move access point to net

* more secrets support

* moving to secrets api

* more comments

* don't throw error

* adding panic message

* use device vs program secrets

* use device secrets for wifi

* adding some kind of system meny for wifi

* update key

* builtin check

* update name

* handle misconfigured wifi

* renaming ocnfig keys

* debug messages

* patch daldts

* text update

* fixing a few settings

* fix merge logic

* adding menu

* export closeMenu

* hide console

* cutify pxt_panic

* refactoring controller into net package

* consolidate abstractions

* refactoring

* rename esp32-game to net-game

* updated menu

* slightly better format

* adding docs

* updated mac address
This commit is contained in:
Peli de Halleux 2019-09-12 10:23:44 -07:00 коммит произвёл GitHub
Родитель 4b76d7e7f3
Коммит ef457fcc73
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
33 изменённых файлов: 802 добавлений и 445 удалений

Просмотреть файл

@ -2,3 +2,7 @@
Azure MQTT communication layer.
A port of https://github.com/rovale/micro-mqtt for MakeCode.
## Settings
The connection string should be stored in the "azureiot" secret in the settings.

Просмотреть файл

@ -5,6 +5,8 @@ const enum AzureIotEvent {
}
namespace azureiot {
export const SECRETS_KEY = "azureiot"
export let logPriority = ConsolePriority.Silent;
type SMap<T> = { [s: string]: T; }
@ -25,12 +27,10 @@ namespace azureiot {
return _mqttClient;
}
export let network: net.Net
export let connString = ""
function createMQTTClient() {
_messageBusId = control.allocateNotifyEvent(); // TODO
const connString = settings.programSecrets.readSecret(SECRETS_KEY, true);
const connStringParts = parsePropertyBag(connString, ";");
const iotHubHostName = connStringParts["HostName"];
const deviceId = connStringParts["DeviceName"];
@ -43,7 +43,7 @@ namespace azureiot {
password: "SharedAccessSignature " + sasToken,
clientId: deviceId
}
const c = new mqtt.Client(opts, network);
const c = new mqtt.Client(opts);
c.on('connected', () => {
log("connected")
control.raiseEvent(_messageBusId, AzureIotEvent.Connected)

Просмотреть файл

@ -10,6 +10,10 @@
],
"public": true,
"dependencies": {
"mqtt": "file:../mqtt"
"mqtt": "file:../mqtt",
"settings": "file:../settings"
},
"testDependencies": {
"esp32": "file:../esp32"
}
}

Просмотреть файл

@ -1,46 +1,23 @@
interface Secrets {
connString: string;
wifi: pxt.StringMap;
}
// this is to be overridden in a separate file
let secrets: Secrets;
function test() {
const log = console.log;
const esp = new esp32spi.SPIController(pins.spi(),
pins.D13, pins.D11, pins.D12, pins.D10, 1)
pins.D13, pins.D11, pins.D12, pins.D10, 1);
if (esp.status != esp32spi.WL_IDLE_STATUS)
if (esp.isIdle)
return
log(`Firmware vers. ${esp.firmwareVersion}`)
log(`MAC addr: ${esp.MACaddress.toHex()}`)
log("Temp: " + esp.getTemperature())
const networks = esp.scanNetworks()
log(JSON.stringify(secrets.wifi))
for (const ap of networks)
log(`\t${ap.ssid}\t\tRSSI: ${ap.rssi}`)
for (let k of Object.keys(secrets.wifi)) {
if (networks.some(n => n.ssid == k)) {
log("connecting to " + k)
esp.connectAP(k, secrets.wifi[k])
break
}
}
if (!esp.isConnected) {
if (!esp.connect()) {
log("can't connect")
return
}
log("ping: " + esp.ping("bing.com"))
azureiot.network = new esp32spi.NetTLS()
azureiot.connString = secrets.connString
azureiot.connect()
log("mqtt connected")

Просмотреть файл

@ -100,6 +100,16 @@
#define CFG_ACCELEROMETER_SPACE 81
#define CFG_PIN_WIFI_MOSI 82
#define CFG_PIN_WIFI_MISO 83
#define CFG_PIN_WIFI_SCK 84
#define CFG_PIN_WIFI_TX 85
#define CFG_PIN_WIFI_RX 86
#define CFG_PIN_WIFI_CS 87
#define CFG_PIN_WIFI_BUSY 88
#define CFG_PIN_WIFI_RESET 89
#define CFG_PIN_WIFI_GPIO0 90
// default I2C address
#define ACCELEROMETER_TYPE_LIS3DH 0x32
#define ACCELEROMETER_TYPE_LIS3DH_ALT 0x30

Просмотреть файл

@ -12,6 +12,48 @@ namespace control {
control.runInParallel(a);
}
export const enum PXT_PANIC {
CODAL_OOM = 20,
GC_OOM = 21,
GC_TOO_BIG_ALLOCATION = 22,
CODAL_HEAP_ERROR = 30,
CODAL_NULL_DEREFERENCE = 40,
CODAL_USB_ERROR = 50,
CODAL_HARDWARE_CONFIGURATION_ERROR = 90,
INVALID_BINARY_HEADER = 901,
OUT_OF_BOUNDS = 902,
REF_DELETED = 903,
SIZE = 904,
INVALID_VTABLE = 905,
INTERNAL_ERROR = 906,
NO_SUCH_CONFIG = 907,
NO_SUCH_PIN = 908,
INVALID_ARGUMENT = 909,
MEMORY_LIMIT_EXCEEDED = 910,
SCREEN_ERROR = 911,
MISSING_PROPERTY = 912,
INVALID_IMAGE = 913,
CALLED_FROM_ISR = 914,
HEAP_DUMPED = 915,
STACK_OVERFLOW = 916,
BLOCKING_TO_STRING = 917,
VM_ERROR = 918,
SETTINGS_CLEARED = 920,
SETTINGS_OVERLOAD = 921,
SETTINGS_SECRET_MISSING = 922,
CAST_FIRST = 980,
CAST_FROM_UNDEFINED = 980,
CAST_FROM_BOOLEAN = 981,
CAST_FROM_NUMBER = 982,
CAST_FROM_STRING = 983,
CAST_FROM_OBJECT = 984,
CAST_FROM_FUNCTION = 985,
CAST_FROM_NULL = 989,
UNHANDLED_EXCEPTION = 999,
}
/**
* Display an error code and stop the program.
* @param code an error number to display. eg: 5

Просмотреть файл

@ -270,6 +270,7 @@ typedef enum {
PANIC_VM_ERROR = 918,
PANIC_SETTINGS_CLEARED = 920,
PANIC_SETTINGS_OVERLOAD = 921,
PANIC_SETTINGS_SECRET_MISSING = 922,
PANIC_CAST_FIRST = 980,
PANIC_CAST_FROM_UNDEFINED = 980,

11
libs/core---samd/dal.d.ts поставляемый
Просмотреть файл

@ -811,6 +811,17 @@ declare const enum DAL {
CFG_PIN_BATTSENSE = 75,
CFG_PIN_VIBRATION = 76,
CFG_PIN_PWREN = 77,
CFG_PIN_ROTARY_ENCODER_A = 79,
CFG_PIN_ROTARY_ENCODER_B = 80,
CFG_PIN_WIFI_MOSI = 82,
CFG_PIN_WIFI_MISO = 83,
CFG_PIN_WIFI_SCK = 84,
CFG_PIN_WIFI_TX = 85,
CFG_PIN_WIFI_RX = 86,
CFG_PIN_WIFI_CS = 87,
CFG_PIN_WIFI_BUSY = 88,
CFG_PIN_WIFI_RESET = 89,
CFG_PIN_WIFI_GPIO0 = 90,
ACCELEROMETER_TYPE_LIS3DH = 50,
ACCELEROMETER_TYPE_MMA8453 = 56,
ACCELEROMETER_TYPE_FXOS8700 = 60,

11
libs/core---stm32/dal.d.ts поставляемый
Просмотреть файл

@ -808,6 +808,17 @@ declare const enum DAL {
CFG_PIN_BATTSENSE = 75,
CFG_PIN_VIBRATION = 76,
CFG_PIN_PWREN = 77,
CFG_PIN_ROTARY_ENCODER_A = 79,
CFG_PIN_ROTARY_ENCODER_B = 80,
CFG_PIN_WIFI_MOSI = 82,
CFG_PIN_WIFI_MISO = 83,
CFG_PIN_WIFI_SCK = 84,
CFG_PIN_WIFI_TX = 85,
CFG_PIN_WIFI_RX = 86,
CFG_PIN_WIFI_CS = 87,
CFG_PIN_WIFI_BUSY = 88,
CFG_PIN_WIFI_RESET = 89,
CFG_PIN_WIFI_GPIO0 = 90,
ACCELEROMETER_TYPE_LIS3DH = 50,
ACCELEROMETER_TYPE_MMA8453 = 56,
ACCELEROMETER_TYPE_FXOS8700 = 60,

11
libs/core/dal.d.ts поставляемый
Просмотреть файл

@ -811,6 +811,17 @@ declare const enum DAL {
CFG_PIN_BATTSENSE = 75,
CFG_PIN_VIBRATION = 76,
CFG_PIN_PWREN = 77,
CFG_PIN_ROTARY_ENCODER_A = 79,
CFG_PIN_ROTARY_ENCODER_B = 80,
CFG_PIN_WIFI_MOSI = 82,
CFG_PIN_WIFI_MISO = 83,
CFG_PIN_WIFI_SCK = 84,
CFG_PIN_WIFI_TX = 85,
CFG_PIN_WIFI_RX = 86,
CFG_PIN_WIFI_CS = 87,
CFG_PIN_WIFI_BUSY = 88,
CFG_PIN_WIFI_RESET = 89,
CFG_PIN_WIFI_GPIO0 = 90,
ACCELEROMETER_TYPE_LIS3DH = 50,
ACCELEROMETER_TYPE_MMA8453 = 56,
ACCELEROMETER_TYPE_FXOS8700 = 60,

25
libs/esp32/README.md Normal file
Просмотреть файл

@ -0,0 +1,25 @@
# ESP32
## ESP32 SPI
Ported from Adafruit Circuit Python
https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI.
## Configuration
* ``PIN_WIFI_CS``, ESP32 CS pin mapping
* ``PIN_WIFI_BUSY``, ESP32 CS pin mapping
* ``PIN_WIFI_RESET``, ESP32 RESET pin mapping
* ``PIN_WIFI_GPIO0`` (optional), ESP32 GPIO0 pin mapping
The driver uses the default SPI pins. You can override this behavior by specifying these 3 keys.
* ``PIN_WIFI_MOSI`` (optional), dedicated SPI MOSI pin
* ``PIN_WIFI_MISO`` (optional), dedicated SPI MISO pin
* ``PIN_WIFI_SCK`` (optional), dedicated SPI SCK pin
## Access Points and passwords
The module uses access points and password information stored in the device secrets. These secrets can be set programmatically using ``net.updateAccessPoint`` or via the menu items in Arcade (added via the ``net-game`` extension).
> *Friendly reminder:* Do not share .uf2 files or programs with secrets!!

Просмотреть файл

@ -1,18 +1,4 @@
namespace esp32spi {
export function monotonic(): number {
return control.millis() / 1000.0;
}
export function print(msg: string) {
console.log(msg);
}
export class AccessPoint {
rssi: number;
encryption: number;
constructor(public ssid: string) { }
}
// pylint: disable=bad-whitespace
const _SET_NET_CMD = 0x10
const _SET_PASSPHRASE_CMD = 0x11
@ -79,10 +65,6 @@ namespace esp32spi {
export const WL_AP_CONNECTED = 8
export const WL_AP_FAILED = 9
export const TCP_MODE = 0
export const UDP_MODE = 1
export const TLS_MODE = 2
function buffer1(ch: number) {
const b = control.createBuffer(1)
@ -90,12 +72,10 @@ namespace esp32spi {
return b
}
export class SPIController {
export class SPIController extends net.Controller {
private _socknum_ll: Buffer[];
private _locked: boolean;
static instance: SPIController;
public wasConnected: boolean;
constructor(
@ -106,12 +86,12 @@ namespace esp32spi {
private _gpio0: DigitalInOutPin = null,
public debug = 0
) {
super();
// if nothing connected, pretend the device is ready -
// we'll check for timeout waiting for response instead
this._busy.setPull(PinPullMode.PullDown);
this._busy.digitalRead();
this._socknum_ll = [buffer1(0)]
SPIController.instance = this;
this._spi.setFrequency(8000000);
this.reset();
this._locked = false;
@ -342,14 +322,14 @@ namespace esp32spi {
/** The results of the latest SSID scan. Returns a list of dictionaries with
'ssid', 'rssi' and 'encryption' entries, one for each AP found
*/
private getScanNetworks(): AccessPoint[] {
private getScanNetworks(): net.AccessPoint[] {
let names = this.sendCommandGetResponse(_SCAN_NETWORKS, undefined, undefined)
// print("SSID names:", names)
// pylint: disable=invalid-name
let APs = []
let i = 0
for (let name of names) {
let a_p = new AccessPoint(name.toString())
let a_p = new net.AccessPoint(name.toString())
let rssi = this.sendCommandGetResponse(_GET_IDX_RSSI_CMD, [buffer1(i)])[0]
a_p.rssi = pins.unpackBuffer("<i", rssi)[0]
let encr = this.sendCommandGetResponse(_GET_IDX_ENCT_CMD, [buffer1(1)])[0]
@ -364,7 +344,7 @@ namespace esp32spi {
Returns a list of dictionaries with 'ssid', 'rssi' and 'encryption' entries,
one for each AP found
*/
public scanNetworks(): AccessPoint[] {
public scanNetworks(): net.AccessPoint[] {
this.startScanNetworks()
// attempts
for (let _ = 0; _ < 10; ++_) {
@ -372,6 +352,10 @@ namespace esp32spi {
// pylint: disable=invalid-name
let APs = this.getScanNetworks()
if (APs) {
if(this.debug) {
for(const ap of APs)
this.log(0, ` ${ap.ssid} => RSSI ${ap.rssi}`)
}
return APs
}
@ -438,12 +422,16 @@ namespace esp32spi {
}
get ssid(): Buffer {
get ssidBuffer(): Buffer {
let resp = this.sendCommandGetResponse(_GET_CURR_SSID_CMD, [hex`ff`])
return resp[0]
}
get ssid(): string {
const b = this.ssidBuffer;
return b ? b.toString() : "";
}
get rssi(): number {
let resp = this.sendCommandGetResponse(_GET_CURR_RSSI_CMD, [hex`ff`])
return pins.unpackBuffer("<i", resp[0])[0]
@ -462,17 +450,33 @@ namespace esp32spi {
return this.status == WL_CONNECTED
}
/** Connect to an access point using a secrets dictionary
that contains a 'ssid' and 'password' entry
*/
public connect(secrets: any): void {
this.connectAP(secrets["ssid"], secrets["password"])
get isIdle(): boolean {
return this.status == WL_IDLE_STATUS;
}
/** Connect to an access point with given name and password.
Will retry up to 10 times and return on success
*/
public connectAP(ssid: string, password: string): number {
/**
* Uses RSSID and password in settings to connect to a compatible AP
*/
public connect(): boolean {
if (this.isConnected) return true;
const wifis = net.knownAccessPoints();
const ssids = Object.keys(wifis);
const networks = this.scanNetworks()
.filter(network => ssids.indexOf(network.ssid) > -1);
const network = networks[0];
if (network)
return this.connectAP(network.ssid, wifis[network.ssid]) == WL_CONNECTED;
// no compatible SSID
return false;
}
/**
* Connect to an access point with given name and password.
* Will retry up to 10 times and return on success
*/
private connectAP(ssid: string, password: string): number {
this.log(0, `Connect to AP ${ssid}`)
if (password) {
this.wifiSetPassphrase(ssid, password)
@ -506,7 +510,10 @@ namespace esp32spi {
a 4 bytearray
*/
public hostbyName(hostname: string): Buffer {
let resp = this.sendCommandGetResponse(_REQ_HOST_BY_NAME_CMD, [control.createBufferFromUTF8(hostname)])
if(!this.connect())
return undefined;
let resp = this.sendCommandGetResponse(_REQ_HOST_BY_NAME_CMD, [control.createBufferFromUTF8(hostname)])
if (resp[0][0] != 1) {
this.fail("Failed to request hostname")
}
@ -519,8 +526,8 @@ namespace esp32spi {
(ttl). Returns a millisecond timing value
*/
public ping(dest: string, ttl: number = 250): number {
if (!this.wasConnected)
return -1
if(!this.connect())
return -1;
// convert to IP address
let ip = this.hostbyName(dest)
@ -535,6 +542,9 @@ namespace esp32spi {
can then be passed to the other socket commands
*/
public socket(): number {
if (!this.connect())
this.fail("can't connect");
this.log(0, "*** Get socket")
let resp0 = this.sendCommandGetResponse(_GET_SOCKET_CMD)
@ -545,7 +555,7 @@ namespace esp32spi {
if (this.debug) {
// %d" % resp)
print("Allocated socket #" + resp)
net.debug("Allocated socket #" + resp)
}
return resp
@ -556,10 +566,10 @@ namespace esp32spi {
'conn_mode' TCP_MODE but can also use UDP_MODE or TLS_MODE
(dest must be hostname for TLS_MODE!)
*/
public socketOpen(socket_num: number, dest: Buffer | string, port: number, conn_mode = TCP_MODE): void {
public socketOpen(socket_num: number, dest: Buffer | string, port: number, conn_mode = net.TCP_MODE): void {
this._socknum_ll[0][0] = socket_num
if (this.debug) {
print("*** Open socket: " + dest + ":" + port)
net.debug("*** Open socket: " + dest + ":" + port)
}
let port_param = pins.packBuffer(">H", [port])
@ -598,7 +608,7 @@ namespace esp32spi {
/** Write the bytearray buffer to a socket */
public socketWrite(socket_num: number, buffer: Buffer): void {
if (this.debug > 1) {
print("Writing:" + buffer.length)
net.debug("Writing:" + buffer.length)
}
this._socknum_ll[0][0] = socket_num
@ -641,15 +651,15 @@ namespace esp32spi {
'conn_mode' TCP_MODE but can also use UDP_MODE or TLS_MODE (dest must
be hostname for TLS_MODE!)
*/
public socketConnect(socket_num: number, dest: string | Buffer, port: number, conn_mode = TCP_MODE): boolean {
public socketConnect(socket_num: number, dest: string | Buffer, port: number, conn_mode = net.TCP_MODE): boolean {
if (this.debug) {
print("*** Socket connect mode " + conn_mode)
net.debug("*** Socket connect mode " + conn_mode)
}
this.socketOpen(socket_num, dest, port, conn_mode)
let times = monotonic()
let times = net.monotonic()
// wait 3 seconds
while (monotonic() - times < 3) {
while (net.monotonic() - times < 3) {
if (this.socket_connected(socket_num)) {
return true
}
@ -664,7 +674,7 @@ namespace esp32spi {
public socketClose(socket_num: number): void {
if (this.debug) {
// %d" % socket_num)
print("*** Closing socket #" + socket_num)
net.debug("*** Closing socket #" + socket_num)
}
this._socknum_ll[0][0] = socket_num

Просмотреть файл

31
libs/esp32/net.ts Normal file
Просмотреть файл

@ -0,0 +1,31 @@
namespace esp32spi {
let _defaultController: SPIController;
function defaultController(): SPIController {
if (_defaultController) return _defaultController;
const cs = pins.pinByCfg(DAL.CFG_PIN_WIFI_CS)
const busy = pins.pinByCfg(DAL.CFG_PIN_WIFI_BUSY);
const reset = pins.pinByCfg(DAL.CFG_PIN_WIFI_RESET);
const gpio0 = pins.pinByCfg(DAL.CFG_PIN_WIFI_GPIO0);
if (!cs || !busy || !reset) {
control.dmesg(`cs ${!!cs} busy ${!!busy} reset ${!!reset}`)
return undefined;
}
const mosi = pins.pinByCfg(DAL.CFG_PIN_WIFI_MOSI);
const miso = pins.pinByCfg(DAL.CFG_PIN_WIFI_MISO);
const sck = pins.pinByCfg(DAL.CFG_PIN_WIFI_SCK);
let spi: SPI;
if (!mosi && !miso && !sck) {
spi = pins.spi();
} else if (mosi && miso && sck) {
spi = pins.createSPI(mosi, miso, sck);
}
if (!spi)
control.panic(control.PXT_PANIC.CODAL_HARDWARE_CONFIGURATION_ERROR);
return _defaultController = new SPIController(spi, cs, busy, reset, gpio0);
}
// initialize net
new net.Net(defaultController);
}

Просмотреть файл

@ -1,11 +1,8 @@
{
"name": "esp32spi",
"name": "esp32",
"description": "ESP32 over SPI - beta",
"files": [
"esp32spi.ts",
"socket.ts",
"requests.ts",
"wifimanager.ts",
"net.ts",
"README.md"
],
@ -15,7 +12,7 @@
"public": true,
"dependencies": {
"core": "file:../core",
"net": "file:../net"
"net": "file:../net",
"settings": "file:../settings"
}
}

22
libs/esp32/test.ts Normal file
Просмотреть файл

@ -0,0 +1,22 @@
function test() {
const log = console.log;
const esp = new esp32spi.SPIController(pins.spi(),
pins.D13, pins.D11, pins.D12, pins.D10, 1)
if (!esp.isIdle)
return
log(`Firmware vers. ${esp.firmwareVersion}`)
log(`MAC addr: ${esp.MACaddress.toHex()}`)
log("Temp: " + esp.getTemperature())
if (!esp.connect()) {
log("can't connect")
return
}
log("ping: " + esp.ping("bing.com"))
}
test();

Просмотреть файл

Просмотреть файл

@ -1,6 +0,0 @@
# WiFi
## ESP32 SPI
Ported from Adafruit Circuit Python
https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI.

Просмотреть файл

@ -1,23 +0,0 @@
namespace esp32spi {
export class Net extends net.Net {
constructor() {
super();
}
createSocket(host: string, port: number): net.Socket {
const socket = new Socket(host, port);
return socket;
}
}
export class NetTLS extends net.Net {
constructor() {
super();
}
createSocket(host: string, port: number): net.Socket {
const socket = new Socket(host, port, TLS_MODE);
return socket;
}
}
}

Просмотреть файл

@ -1,256 +0,0 @@
namespace esp32spi {
export class Response {
socket: Socket
_cached: Buffer
status_code: number
reason: string
_read_so_far: number
headers: StringMap
/** The response from a request, contains all the headers/content */
constructor(sock: Socket) {
this.socket = sock
this._cached = null
this.status_code = null
this.reason = null
this._read_so_far = 0
this.headers = {}
}
public close() {
/** Close, delete and collect the response data */
if (this.socket) {
this.socket.close()
this.socket = null
}
this._cached = null
}
get content() {
/** The HTTP content direct from the socket, as bytes */
let content_length = parseInt(this.headers["content-length"]) || 0
// print("Content length:", content_length)
if (this._cached === null) {
this._cached = this.socket.read(content_length)
this.socket.close()
this.socket = null
}
// print("Buffer length:", len(self._cached))
return this._cached
}
/** The HTTP content, encoded into a string according to the HTTP
header encoding
*/
get text() {
return this.content.toString()
}
get json() {
return JSON.parse(this.text)
}
public toString() {
return `HTTP ${this.status_code}; ${Object.keys(this.headers).length} headers; ${this._cached ? this._cached.length : -1} bytes content`
}
}
export type StringMap = { [v: string]: string; };
export interface RequestOptions {
data?: string | Buffer;
json?: any; // will call JSON.stringify()
headers?: StringMap;
stream?: boolean;
timeout?: number; // in ms
}
export function dataAsBuffer(data: string | Buffer): Buffer {
if (data == null)
return null
if (typeof data == "string")
return control.createBufferFromUTF8(data)
return data
}
// TODO move to PXT
// also note this doesn't handle unicode, but neither does JS (there's toLocaleLowerCase())
export function toLowerCase(s: string) {
let r = ""
let prev = 0
for (let i = 0; i < s.length; i++) {
const c = s.charCodeAt(i)
if (65 <= c && c <= 90) {
r += s.slice(prev, i) + String.fromCharCode(c + 32)
prev = i + 1
}
}
r += s.slice(prev)
return r
}
/*
>>> "a,b,c,d,e".split(",", 2)
['a', 'b', 'c,d,e']
*/
function pysplit(str: string, sep:string, limit: number) {
const arr = str.split(sep)
if (arr.length >= limit) {
return arr.slice(0, limit).concat([arr.slice(limit).join(sep)])
} else {
return arr
}
}
/** Perform an HTTP request to the given url which we will parse to determine
whether to use SSL ('https://') or not. We can also send some provided 'data'
or a json dictionary which we will stringify. 'headers' is optional HTTP headers
sent along. 'stream' will determine if we buffer everything, or whether to only
read only when requested
*/
export function request(method: string, url: string, options?: RequestOptions): Response {
if (!options) options = {};
if (!options.headers) {
options.headers = {}
}
const tmp = pysplit(url, "/", 3)
let proto = tmp[0]
let host = tmp[2]
let path = tmp[3] || ""
// replace spaces in path
// TODO
// path = path.replace(" ", "%20")
let port = 0
if (proto == "http:") {
port = 80
} else if (proto == "https:") {
port = 443
} else {
control.fail("Unsupported protocol: " + proto)
}
if (host.indexOf(":") >= 0) {
const tmp = host.split(":")
host = tmp[0]
port = parseInt(tmp[1])
}
let ipaddr = esp32spi.SPIController.instance.hostbyName(host)
let sock: Socket;
let conntype = esp32spi.TCP_MODE
if (proto == "https:") {
conntype = esp32spi.TLS_MODE
// for SSL we need to know the host name
sock = new Socket(host, port, conntype)
} else {
sock = new Socket(ipaddr, port, conntype)
}
// our response
let resp = new Response(sock)
// socket read timeout
sock.setTimeout(options.timeout)
sock.connect();
sock.send(`${method} /${path} HTTP/1.0\r\n`)
if (!options.headers["Host"])
sock.send(`Host: ${host}\r\n`)
if (!options.headers["User-Agent"])
sock.send("User-Agent: MakeCode ESP32\r\n")
// Iterate over keys to avoid tuple alloc
for (let k of Object.keys(options.headers))
sock.send(`${k}: ${options.headers[k]}\r\n`)
if (options.json != null) {
control.assert(options.data == null, 100)
options.data = JSON.stringify(options.json)
sock.send("Content-Type: application/json\r\n")
}
let dataBuf = dataAsBuffer(options.data)
if (dataBuf)
sock.send(`Content-Length: ${dataBuf.length}\r\n`)
sock.send("\r\n")
if (dataBuf)
sock.send(dataBuf)
let line = sock.readLine()
// print(line)
let line2 = pysplit(line, " ", 2)
let status = parseInt(line2[1])
let reason = ""
if (line2.length > 2) {
reason = line2[2]
}
while (true) {
line = sock.readLine()
if (!line || line == "\r\n") {
break
}
// print("**line: ", line)
const tmp = pysplit(line, ": ", 1)
let title = tmp[0]
let content = tmp[1]
if (title && content) {
resp.headers[toLowerCase(title)] = toLowerCase(content)
}
}
/*
elif line.startswith(b"Location:") and not 200 <= status <= 299:
raise NotImplementedError("Redirects not yet supported")
*/
if ((resp.headers["transfer-encoding"] || "").indexOf("chunked") >= 0)
control.fail("not supported chunked encoding")
resp.status_code = status
resp.reason = reason
return resp
}
/** Send HTTP HEAD request */
export function head(url: string, options?: RequestOptions) {
return request("HEAD", url, options)
}
/** Send HTTP GET request */
export function get(url: string, options?: RequestOptions) {
return request("GET", url, options)
}
/** Send HTTP POST request */
export function post(url: string, options?: RequestOptions) {
return request("POST", url, options)
}
/** Send HTTP PATCH request */
export function patch(url: string, options?: RequestOptions) {
return request("PATCH", url, options)
}
/** Send HTTP PUT request */
export function put(url: string, options?: RequestOptions) {
return request("PUT", url, options)
}
/** Send HTTP DELETE request */
export function del(url: string, options?: RequestOptions) {
return request("DELETE", url, options)
}
}

Просмотреть файл

@ -1,43 +0,0 @@
export interface Secrets {
connString: string;
wifi: pxt.StringMap;
}
// this is to be overridden in a separate file
export let secrets: Secrets;
function test() {
const log = console.log;
const esp = new esp32spi.SPIController(pins.spi(),
pins.D13, pins.D11, pins.D12, pins.D10, 1)
if (esp.status != esp32spi.WL_IDLE_STATUS)
return
log(`Firmware vers. ${esp.firmwareVersion}`)
log(`MAC addr: ${esp.MACaddress.toHex()}`)
log("Temp: " + esp.getTemperature())
const networks = esp.scanNetworks()
log(JSON.stringify(secrets.wifi))
for (const ap of networks)
log(`\t${ap.ssid}\t\tRSSI: ${ap.rssi}`)
for (let k of Object.keys(secrets.wifi)) {
if (networks.some(n => n.ssid == k)) {
log("connecting to " + k)
esp.connectAP(k, secrets.wifi[k])
break
}
}
if (!esp.isConnected) {
log("can't connect")
return
}
log("ping: " + esp.ping("bing.com"))
}
test();

Просмотреть файл

@ -308,7 +308,7 @@ namespace scene.systemMenu {
power.deepSleep();
}
function closeMenu() {
export function closeMenu() {
if (instance) {
instance.dispose();
instance = undefined;

Просмотреть файл

@ -320,7 +320,7 @@ namespace mqtt {
private mqttHandlers: MQTTHandler[];
constructor(opt: IConnectionOptions, net: net.Net) {
constructor(opt: IConnectionOptions) {
super();
this.wdId = Constants.Uninitialized;
@ -336,7 +336,7 @@ namespace mqtt {
}
this.opt = opt;
this.net = net;
this.net = net.Net.instance;
}
private static describe(code: ConnectReturnCode): string {
@ -396,7 +396,7 @@ namespace mqtt {
}, Constants.WatchDogInterval * 1000);
}
this.sct = this.net.createSocket(this.opt.host, this.opt.port);
this.sct = this.net.createSocket(this.opt.host, this.opt.port, true);
this.sct.onOpen(() => {
this.log('Network connection established.');
this.emit('connect');

13
libs/net-game/pxt.json Normal file
Просмотреть файл

@ -0,0 +1,13 @@
{
"name": "net-game",
"description": "WiFi support in Arcade - beta",
"files": [
"settings.ts"
],
"public": true,
"dependencies": {
"core": "file:../core",
"settings": "file:../settings",
"game": "file:../game"
}
}

144
libs/net-game/settings.ts Normal file
Просмотреть файл

@ -0,0 +1,144 @@
namespace net {
class Configurator {
private accessPoints: net.AccessPoint[];
private apIndex: number;
private scanning: boolean;
private wifi: net.Controller;
constructor() {
this.scanning = false;
this.apIndex = 0;
}
private select() {
const ap = this.accessPoints && this.accessPoints[this.apIndex];
if (ap) {
const wifis = net.knownAccessPoints();
const known = wifis[ap.ssid] !== undefined ? "*" : "?";
console.log(`${known} ${ap.ssid}`);
}
}
private connect() {
console.log("connecting...")
this.wifi.connect();
console.log(this.wifi.isConnected ? `connected to ${this.wifi.ssid}` : `disconnected`);
if (this.wifi.isConnected) {
for (let i = 0; i < 3; ++i) {
const ping = this.wifi.ping("bing.com")
console.log(`bing.com ping ${ping}ms`);
}
}
}
private scan() {
if (this.scanning) return;
this.scanning = true;
const mac = this.wifi.MACaddress;
console.log(`MAC: ${mac ? mac.toHex() : "???"}`)
console.log("scanning...")
control.runInBackground(() => {
this.accessPoints = this.wifi.scanNetworks()
if (this.accessPoints && this.accessPoints.length) {
const wifis = net.knownAccessPoints();
for (let i = 0; i < this.accessPoints.length; ++i) {
const ap = this.accessPoints[i];
const known = wifis[ap.ssid] !== undefined ? "*" : "?";
console.log(` ${known} ${ap.ssid}`);
}
console.log(" ");
this.apIndex = 0;
console.log("*: AP known")
console.log("up/down: select AP")
console.log("left: erase AP info")
console.log("right: enter AP password")
console.log("A: connect")
console.log(" ");
this.select();
}
this.scanning = false;
});
}
main() {
this.wifi = net.Net.instance.controller;
if (!this.wifi) {
console.log("WiFi module not configured");
return;
}
pauseUntil(() => this.wifi.isIdle, 30000);
if (!this.wifi.isIdle) {
console.log("WiFi module not responding")
return;
}
controller.up.onEvent(ControllerButtonEvent.Pressed, () => {
this.apIndex = this.apIndex + 1;
if (this.accessPoints)
this.apIndex = this.apIndex % this.accessPoints.length;
this.select();
})
controller.down.onEvent(ControllerButtonEvent.Pressed, () => {
this.apIndex = this.apIndex - 1;
this.apIndex = (this.apIndex + this.accessPoints.length) % this.accessPoints.length;
this.select();
})
controller.left.onEvent(ControllerButtonEvent.Pressed, () => {
const ap = this.accessPoints && this.accessPoints[this.apIndex];
if (!ap) return;
net.updateAccessPoint(ap.ssid, undefined);
console.log(`password erased`)
this.scan();
})
controller.right.onEvent(ControllerButtonEvent.Pressed, () => {
const ap = this.accessPoints && this.accessPoints[this.apIndex];
if (!ap) return;
game.consoleOverlay.setVisible(false);
const pwd = game.askForString(`password for ${ap.ssid}`, 24);
game.consoleOverlay.setVisible(true);
net.updateAccessPoint(ap.ssid, pwd);
console.log(`password saved`)
this.scan();
})
controller.A.onEvent(ControllerButtonEvent.Pressed, () => {
this.connect();
})
controller.B.onEvent(ControllerButtonEvent.Pressed, () => {
game.popScene();
game.consoleOverlay.setVisible(false);
});
this.scan();
}
}
function wifiSystemMenu() {
scene.systemMenu.closeMenu();
game.pushScene();
game.consoleOverlay.setVisible(true);
console.log("WiFi configuration")
const config = new Configurator();
config.main()
}
scene.systemMenu.addEntry(
() => "WiFi",
() => wifiSystemMenu(),
img`
. . . . . . . . . . . . . . . .
. . . . . . 8 8 8 8 . . . . . .
. . . . 8 8 8 6 6 6 8 8 . . . .
. . . 8 6 6 6 6 6 6 6 6 8 . . .
. . 8 6 6 . . . . . . 6 6 8 . .
. 8 6 6 . . . . . . . . 6 6 8 .
8 6 6 . . . 8 8 8 8 . . . 6 6 8
. 6 . . . 8 6 6 6 6 8 . . . 6 .
. . . . 8 6 6 6 6 6 6 8 . . . .
. . . 8 6 6 . . . . 6 6 8 . . .
. . . . 6 . . . . . . 6 . . . .
. . . . . . . 8 8 . . . . . . .
. . . . . . 8 6 6 8 . . . . . .
. . . . . . 6 6 6 6 . . . . . .
. . . . . . . 6 6 . . . . . . .
. . . . . . . . . . . . . . . .
`);
}

Просмотреть файл

@ -1,3 +1,22 @@
# Net
Networking abstractions
Networking abstractions and drivers
## WiFi module configuration
* ``PIN_WIFI_CS``, ESP32 CS pin mapping
* ``PIN_WIFI_BUSY``, ESP32 CS pin mapping
* ``PIN_WIFI_RESET``, ESP32 RESET pin mapping
* ``PIN_WIFI_GPIO0`` (optional), ESP32 GPIO0 pin mapping
The driver uses the default SPI pins. You can override this behavior by specifying these 3 keys.
* ``PIN_WIFI_MOSI`` (optional), dedicated SPI MOSI pin
* ``PIN_WIFI_MISO`` (optional), dedicated SPI MISO pin
* ``PIN_WIFI_SCK`` (optional), dedicated SPI SCK pin
## Access Points and passwords
The module uses access points and password information stored in the device secrets. These secrets can be set programmatically using ``net.updateAccessPoint`` or via the menu items in Arcade (added via the ``net-game`` extension).
> *Friendly reminder:* Do not share .uf2 files or programs with secrets!!

41
libs/net/controller.ts Normal file
Просмотреть файл

@ -0,0 +1,41 @@
namespace net {
export class Controller {
constructor() { }
public scanNetworks(): net.AccessPoint[] {
return [];
}
public socket(): number {
return -1;
}
public socketConnect(socket_num: number, dest: string | Buffer, port: number, conn_mode = TCP_MODE): boolean {
return false;
}
public socketWrite(socket_num: number, buffer: Buffer): void {
}
public socketAvailable(socket_num: number): number {
return -1;
}
public socketRead(socket_num: number, size: number): Buffer {
return undefined;
}
public socketClose(socket_num: number): void {
}
public hostbyName(hostname: string): Buffer {
return undefined;
}
get isIdle(): boolean { return false; }
get isConnected(): boolean { return false; }
connect(): void { }
get ssid(): string { return undefined; }
get MACaddress(): Buffer { return undefined; }
public ping(dest: string, ttl: number = 250): number { return -1; }
}
}

Просмотреть файл

@ -1,10 +1,13 @@
namespace esp32spi {
namespace net {
export const SOCK_STREAM = 1
export const AF_INET = 2
export const MAX_PACKET = 4000
export const TCP_MODE = 0
export const UDP_MODE = 1
export const TLS_MODE = 2
export class Socket implements net.Socket {
export class ControllerSocket implements net.Socket {
_buffer: Buffer;
_socknum: number;
_timeout: number;
@ -17,12 +20,12 @@ namespace esp32spi {
/** A simplified implementation of the Python 'socket' class, for connecting
through an interface to a remote device
*/
constructor(private host: string | Buffer, private port: number, private conntype: number = null) {
constructor(private controller: Controller, private host: string | Buffer, private port: number, private conntype: number = null) {
if (this.conntype === null) {
this.conntype = esp32spi.TCP_MODE
this.conntype = net.TCP_MODE
}
this._buffer = hex``
this._socknum = esp32spi.SPIController.instance.socket()
this._socknum = this.controller.socket()
this.setTimeout(0)
}
@ -31,7 +34,7 @@ namespace esp32spi {
depending on the underlying interface
*/
public connect() {
if (!esp32spi.SPIController.instance.socketConnect(this._socknum, this.host, this.port, this.conntype)) {
if (!this.controller.socketConnect(this._socknum, this.host, this.port, this.conntype)) {
this.error(`failed to connect to ${this.host}`)
return;
}
@ -45,7 +48,7 @@ namespace esp32spi {
/** Send some data to the socket */
public send(data: string | Buffer) {
//console.log("sock wr: " + data)
esp32spi.SPIController.instance.socketWrite(this._socknum, dataAsBuffer(data))
this.controller.socketWrite(this._socknum, net.dataAsBuffer(data))
}
private error(msg: string) {
@ -85,9 +88,9 @@ namespace esp32spi {
let stamp = monotonic()
while (this._buffer.indexOf(hex`0d0a`) < 0) {
// there's no line already in there, read some more
let avail = Math.min(esp32spi.SPIController.instance.socketAvailable(this._socknum), MAX_PACKET)
let avail = Math.min(this.controller.socketAvailable(this._socknum), MAX_PACKET)
if (avail) {
this._buffer = this._buffer.concat(esp32spi.SPIController.instance.socketRead(this._socknum, avail))
this._buffer = this._buffer.concat(this.controller.socketRead(this._socknum, avail))
} else if (this._timeout > 0 && monotonic() - stamp > this._timeout) {
// Make sure to close socket so that we don't exhaust sockets.
this.close()
@ -107,9 +110,9 @@ namespace esp32spi {
// print("Socket read", size)
if (size == 0) {
if (this._buffer.length == 0) {
let avail = Math.min(esp32spi.SPIController.instance.socketAvailable(this._socknum), MAX_PACKET)
let avail = Math.min(this.controller.socketAvailable(this._socknum), MAX_PACKET)
if (avail)
this._buffer = this._buffer.concat(esp32spi.SPIController.instance.socketRead(this._socknum, avail))
this._buffer = this._buffer.concat(this.controller.socketRead(this._socknum, avail))
}
let ret = this._buffer
this._buffer = hex``
@ -121,10 +124,10 @@ namespace esp32spi {
let received = []
while (to_read > 0) {
// print("Bytes to read:", to_read)
let avail = Math.min(esp32spi.SPIController.instance.socketAvailable(this._socknum), MAX_PACKET)
let avail = Math.min(this.controller.socketAvailable(this._socknum), MAX_PACKET)
if (avail) {
stamp = monotonic()
let recv = esp32spi.SPIController.instance.socketRead(this._socknum, Math.min(to_read, avail))
let recv = this.controller.socketRead(this._socknum, Math.min(to_read, avail))
received.push(recv)
to_read -= recv.length
}
@ -157,7 +160,7 @@ namespace esp32spi {
/** Close the socket, after reading whatever remains */
public close() {
this._closed = true;
esp32spi.SPIController.instance.socketClose(this._socknum)
this.controller.socketClose(this._socknum)
if (this._closeHandler)
this._closeHandler();
}

Просмотреть файл

@ -1,7 +1,20 @@
namespace net {
export let logPriority = ConsolePriority.Silent;
export function log(msg: string) {
console.add(logPriority, `net: ` + msg);
console.add(logPriority, "net:" + msg);
}
export function debug(msg: string) {
console.add(ConsolePriority.Debug, "net:" + msg);
}
export function monotonic(): number {
return control.millis() / 1000.0;
}
export class AccessPoint {
rssi: number;
encryption: number;
constructor(public ssid: string) { }
}
export interface Socket {
@ -13,12 +26,51 @@ namespace net {
onClose(handler: () => void): void;
onError(handler: (msg: string) => void): void;
onMessage(handler: (data: Buffer) => void): void;
setTimeout(millis: number): void;
readLine(): string;
}
export class Net {
constructor() {}
createSocket(host: string, port: number): Socket {
private _controller: Controller;
constructor(private factory: () => Controller) {
Net.instance = this;
}
static instance: Net;
get controller(): net.Controller {
if (!this._controller)
this._controller = this.factory();
return this._controller;
}
createSocket(host: string, port: number, secure: boolean): net.Socket {
const c = this.controller;
if (!c) return undefined;
const socket = new net.ControllerSocket(c, host, port, secure ? net.TLS_MODE : net.TCP_MODE);
return socket;
}
hostByName(host: string): string {
const c= this.controller;
if (!c) return undefined;
const b = this.controller.hostbyName(host);
if (b) return b.toString();
return undefined;
}
}
const AP_SECRETS_KEY = "wifi";
/**
* Gets the map of SSID -> password pairs
*/
export function knownAccessPoints(): StringMap {
return settings.deviceSecrets.readSecret(AP_SECRETS_KEY) || {};
}
export function updateAccessPoint(ssid: string, password: string) {
const k: StringMap = {};
k[ssid] = password;
settings.deviceSecrets.updateSecret(AP_SECRETS_KEY, k);
}
}

Просмотреть файл

@ -3,11 +3,14 @@
"description": "Networking abstractions",
"files": [
"README.md",
"controller.ts",
"controllersocket.ts",
"net.ts",
"requests.ts"
],
"public": true,
"dependencies": {
"core": "file:../core"
"core": "file:../core",
"settings": "file:../settings"
}
}

Просмотреть файл

@ -6,7 +6,9 @@ namespace net {
_read_so_far: number
headers: StringMap;
/** The response from a request, contains all the headers/content */
/**
* The response from a request, contains all the headers/content
*/
constructor(private socket: Socket) {
this._cached = null
this.status_code = null
@ -15,7 +17,9 @@ namespace net {
this.headers = {}
}
/** Close, delete and collect the response data */
/**
* Close, delete and collect the response data
*/
public close() {
if (this.socket) {
this.socket.close()
@ -24,7 +28,9 @@ namespace net {
this._cached = null
}
/** The HTTP content direct from the socket, as bytes */
/**
* The HTTP content direct from the socket, as bytes
*/
get content() {
let content_length = parseInt(this.headers["content-length"]) || 0
@ -46,9 +52,13 @@ namespace net {
return this.content.toString()
}
public json() {
get json() {
return JSON.parse(this.text)
}
public toString() {
return `HTTP ${this.status_code}; ${Object.keys(this.headers).length} headers; ${this._cached ? this._cached.length : -1} bytes content`
}
}
export interface RequestOptions {
@ -66,4 +76,164 @@ namespace net {
return control.createBufferFromUTF8(data)
return data
}
/*
>>> "a,b,c,d,e".split(",", 2)
['a', 'b', 'c,d,e']
*/
function pysplit(str: string, sep: string, limit: number) {
const arr = str.split(sep)
if (arr.length >= limit) {
return arr.slice(0, limit).concat([arr.slice(limit).join(sep)])
} else {
return arr
}
}
/** Perform an HTTP request to the given url which we will parse to determine
whether to use SSL ('https://') or not. We can also send some provided 'data'
or a json dictionary which we will stringify. 'headers' is optional HTTP headers
sent along. 'stream' will determine if we buffer everything, or whether to only
read only when requested
*/
export function request(method: string, url: string, options?: RequestOptions): net.Response {
if (!options) options = {};
if (!options.headers) {
options.headers = {}
}
const tmp = pysplit(url, "/", 3)
let proto = tmp[0]
let host = tmp[2]
let path = tmp[3] || ""
// replace spaces in path
// TODO
// path = path.replace(" ", "%20")
let port = 0
if (proto == "http:") {
port = 80
} else if (proto == "https:") {
port = 443
} else {
control.fail("Unsupported protocol: " + proto)
}
if (host.indexOf(":") >= 0) {
const tmp = host.split(":")
host = tmp[0]
port = parseInt(tmp[1])
}
let ipaddr = net.Net.instance.hostByName(host)
let sock: Socket;
if (proto == "https:") {
// for SSL we need to know the host name
sock = net.Net.instance.createSocket(host, port, true)
} else {
sock = net.Net.instance.createSocket(ipaddr, port, false)
}
// our response
let resp = new Response(sock)
// socket read timeout
sock.setTimeout(options.timeout)
sock.connect();
sock.send(`${method} /${path} HTTP/1.0\r\n`)
if (!options.headers["Host"])
sock.send(`Host: ${host}\r\n`)
if (!options.headers["User-Agent"])
sock.send("User-Agent: MakeCode ESP32\r\n")
// Iterate over keys to avoid tuple alloc
for (let k of Object.keys(options.headers))
sock.send(`${k}: ${options.headers[k]}\r\n`)
if (options.json != null) {
control.assert(options.data == null, 100)
options.data = JSON.stringify(options.json)
sock.send("Content-Type: application/json\r\n")
}
let dataBuf = dataAsBuffer(options.data)
if (dataBuf)
sock.send(`Content-Length: ${dataBuf.length}\r\n`)
sock.send("\r\n")
if (dataBuf)
sock.send(dataBuf)
let line = sock.readLine()
// print(line)
let line2 = pysplit(line, " ", 2)
let status = parseInt(line2[1])
let reason = ""
if (line2.length > 2) {
reason = line2[2]
}
while (true) {
line = sock.readLine()
if (!line || line == "\r\n") {
break
}
// print("**line: ", line)
const tmp = pysplit(line, ": ", 1)
let title = tmp[0]
let content = tmp[1]
if (title && content) {
resp.headers[title.toLowerCase()] = content.toLowerCase()
}
}
/*
elif line.startswith(b"Location:") and not 200 <= status <= 299:
raise NotImplementedError("Redirects not yet supported")
*/
if ((resp.headers["transfer-encoding"] || "").indexOf("chunked") >= 0)
control.fail("not supported chunked encoding")
resp.status_code = status
resp.reason = reason
return resp
}
/** Send HTTP HEAD request */
export function head(url: string, options?: RequestOptions) {
return request("HEAD", url, options)
}
/** Send HTTP GET request */
export function get(url: string, options?: RequestOptions) {
return request("GET", url, options)
}
/** Send HTTP POST request */
export function post(url: string, options?: RequestOptions) {
return request("POST", url, options)
}
/** Send HTTP PATCH request */
export function patch(url: string, options?: RequestOptions) {
return request("PATCH", url, options)
}
/** Send HTTP PUT request */
export function put(url: string, options?: RequestOptions) {
return request("PUT", url, options)
}
/** Send HTTP DELETE request */
export function del(url: string, options?: RequestOptions) {
return request("DELETE", url, options)
}
}

Просмотреть файл

@ -1,4 +1,9 @@
namespace settings {
const RUN_KEY = "#run";
const SCOPE_KEY = "#scope";
const DEVICE_SECRETS_KEY = "#secrets";
const SECRETS_KEY = "__secrets";
//% shim=pxt::seedAddRandom
declare function seedAddRandom(n: number): void;
@ -21,22 +26,22 @@ namespace settings {
declare function _list(prefix: string): string[];
export function runNumber() {
return readNumber("#run") || 0
return readNumber(RUN_KEY) || 0
}
function setScope(scope: string) {
if (!scope || scope.length > 100)
control.panic(922)
const currScope = readString("#scope")
const currScope = readString(SCOPE_KEY)
if (currScope != scope) {
_userClean()
writeString("#scope", scope)
writeString(SCOPE_KEY, scope)
}
}
function initScopes() {
const rn = runNumber() + 1
writeNumber("#run", rn)
writeNumber(RUN_KEY, rn)
seedAddRandom(control.deviceSerialNumber() & 0x7fffffff)
seedAddRandom(rn)
@ -151,4 +156,82 @@ namespace settings {
export function exists(key: string) {
return _exists(key)
}
function clone(v: any): any {
if (v == null) return null
return JSON.parse(JSON.stringify(v))
}
function isKV(v: any) {
return !!v && typeof v === "object" && !Array.isArray(v)
}
function jsonMergeFrom(trg: any, src: any) {
if (!src) return;
const keys = Object.keys(src)
keys.forEach(k => {
const srck = src[k];
if (isKV(trg[k]) && isKV(srck))
jsonMergeFrom(trg[k], srck);
else
trg[k] = clone(srck);
});
}
//% fixedInstances
export class SecretStore {
constructor(private key: string) { }
setSecret(name: string, value: any) {
const secrets = this.readSecrets();
secrets[name] = value;
writeString(this.key, JSON.stringify(secrets));
}
updateSecret(name: string, value: any) {
const secrets = this.readSecrets();
const secret = secrets[name];
if (secret === undefined)
secrets[name] = value;
else jsonMergeFrom(secret, value);
const v = JSON.stringify(secrets);
writeString(this.key, v);
}
readSecret(name: string, ensure: boolean = false): any {
const secrets = this.readSecrets();
const secret = secrets[name];
if (ensure && !secret) {
control.dmesg("missing secret " + name);
control.panic(control.PXT_PANIC.SETTINGS_SECRET_MISSING);
}
return secret;
}
clearSecrets() {
writeString(this.key, "{}");
}
readSecrets(): any {
try {
const src = readString(this.key) || "{}";
return JSON.parse(src) || {};
} catch {
control.dmesg("invalid secret format")
return {};
}
}
}
/**
* Secrets shared by any program on the device
*/
//% fixedInstance whenUsed block="device secrets"
export const deviceSecrets = new SecretStore(DEVICE_SECRETS_KEY);
/**
* Program secrets
*/
//% fixedInstance whenUsed block="program secrets"
export const programSecrets = new SecretStore(SECRETS_KEY);
}

Просмотреть файл

@ -43,8 +43,9 @@
"libs/edge-connector",
"libs/proximity",
"libs/feather",
"libs/esp32spi",
"libs/esp32",
"libs/net",
"libs/net-game",
"libs/mqtt",
"libs/azureiot",
"libs/color",